Skip to content

Commit

Permalink
Replace the stack used for sibling search with a heap
Browse files Browse the repository at this point in the history
  • Loading branch information
Sopiro committed Mar 28, 2024
1 parent 5db506c commit 5e3e7f8
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 40 deletions.
3 changes: 3 additions & 0 deletions out/aabb.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class AABB {
get area() {
return (this.max.x - this.min.x) * (this.max.y - this.min.y);
}
get perimeter() {
return 2 * ((this.max.x - this.min.x) + (this.max.y - this.min.y));
}
}
export function fix(aabb) {
let minX = Math.min(aabb.min.x, aabb.max.x);
Expand Down
42 changes: 23 additions & 19 deletions out/aabbtree.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { union } from "./aabb.js";
import { Heap } from "./heap.js";
import { Settings } from "./settings.js";
import { assert, make_pair_natural } from "./util.js";
export class AABBTree {
Expand Down Expand Up @@ -71,21 +72,24 @@ export class AABBTree {
let aabb = leaf.aabb;
// Find the best sibling for the new leaf
let bestSibling = this.root;
let bestCost = union(this.root.aabb, aabb).area;
let q = [{ p1: this.root, p2: 0.0 }];
while (q.length != 0) {
let front = q.shift();
let bestCost = union(this.root.aabb, aabb).perimeter;
// let q: Pair<Node, number>[] = [];
// Using priority queue
let q = new Heap([], (a, b) => { return a.p2 < b.p2; });
q.push({ p1: this.root, p2: 0.0 });
while (q.length > 0) {
let front = q.pop();
let current = front.p1;
let inheritedCost = front.p2;
let combined = union(current.aabb, aabb);
let directCost = combined.area;
let directCost = combined.perimeter;
let costForCurrent = directCost + inheritedCost;
if (costForCurrent < bestCost) {
bestCost = costForCurrent;
bestSibling = current;
}
inheritedCost += directCost - current.aabb.area;
let lowerBoundCost = aabb.area + inheritedCost;
inheritedCost += directCost - current.aabb.perimeter;
let lowerBoundCost = aabb.perimeter + inheritedCost;
if (lowerBoundCost < bestCost) {
if (!current.isLeaf) {
q.push({ p1: current.child1, p2: inheritedCost });
Expand Down Expand Up @@ -169,14 +173,14 @@ export class AABBTree {
let child2 = node.child2;
let costDiffs = [0, 0, 0, 0];
if (child1.isLeaf == false) {
let area1 = child1.aabb.area;
costDiffs[0] = union(child1.child1.aabb, child2.aabb).area - area1;
costDiffs[1] = union(child1.child2.aabb, child2.aabb).area - area1;
let area1 = child1.aabb.perimeter;
costDiffs[0] = union(child1.child1.aabb, child2.aabb).perimeter - area1;
costDiffs[1] = union(child1.child2.aabb, child2.aabb).perimeter - area1;
}
if (child2.isLeaf == false) {
let area2 = child2.aabb.area;
costDiffs[2] = union(child2.child1.aabb, child1.aabb).area - area2;
costDiffs[3] = union(child2.child2.aabb, child1.aabb).area - area2;
let area2 = child2.aabb.perimeter;
costDiffs[2] = union(child2.child1.aabb, child1.aabb).perimeter - area2;
costDiffs[3] = union(child2.child2.aabb, child1.aabb).perimeter - area2;
}
let bestDiffIndex = 0;
for (let i = 1; i < 4; i++) {
Expand Down Expand Up @@ -273,18 +277,18 @@ export class AABBTree {
if (this.root == undefined) {
return res;
}
let q = [this.root];
while (q.length != 0) {
let current = q.shift();
let stack = [this.root];
while (stack.length != 0) {
let current = stack.pop();
if (!current.aabb.testOverlap(region)) {
continue;
}
if (current.isLeaf) {
res.push(current);
}
else {
q.push(current.child1);
q.push(current.child2);
stack.push(current.child1);
stack.push(current.child2);
}
}
return res;
Expand Down Expand Up @@ -356,7 +360,7 @@ export class AABBTree {
get cost() {
let cost = 0;
this.traverse(node => {
cost += node.aabb.area;
cost += node.aabb.perimeter;
});
return cost;
}
Expand Down
2 changes: 1 addition & 1 deletion out/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export class Game {
}
}
updateCostLabel() {
this.surfaceAreaLabel.innerHTML = "Tree surface area: " + this.tree.cost.toFixed(2);
this.surfaceAreaLabel.innerHTML = "Tree cost: " + this.tree.cost.toFixed(2);
}
render(r) {
r.setCameraTransform(this.camera.cameraTransform);
Expand Down
57 changes: 57 additions & 0 deletions out/heap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
export class Heap {
constructor(array, comparator) {
this.heap = array;
this.compare = comparator;
this.heapify();
}
push(value) {
this.heap.push(value);
let index = this.heap.length - 1;
while (index > 0) {
let parent = Math.trunc((index - 1) / 2);
if (this.compare(this.heap[parent], this.heap[index])) {
break;
}
let tmp = this.heap[parent];
this.heap[parent] = this.heap[index];
this.heap[index] = tmp;
index = parent;
}
}
pop() {
let value = this.heap[0];
this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();
this.downHeap(0);
return value;
}
downHeap(index) {
let size = this.length;
let largest = index;
let left = 2 * index + 1;
let right = 2 * index + 2;
if (left < size && this.compare(this.heap[left], this.heap[largest])) {
largest = left;
}
if (right < size && this.compare(this.heap[right], this.heap[largest])) {
largest = right;
}
if (largest != index) {
// swap
let tmp = this.heap[index];
this.heap[index] = this.heap[largest];
this.heap[largest] = tmp;
// recurse down
this.downHeap(largest);
}
}
heapify() {
// start from middle
for (let i = Math.trunc((this.length / 2) - 1); i >= 0; --i) {
this.downHeap(i);
}
}
get length() {
return this.heap.length;
}
}
5 changes: 5 additions & 0 deletions src/aabb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export class AABB
{
return (this.max.x - this.min.x) * (this.max.y - this.min.y);
}

get perimeter(): number
{
return 2 * ((this.max.x - this.min.x) + (this.max.y - this.min.y));
}
}

export function fix(aabb: AABB): void
Expand Down
45 changes: 26 additions & 19 deletions src/aabbtree.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AABB, union } from "./aabb.js";
import { Heap } from "./heap.js";
import { Vector2 } from "./math.js";
import { Settings } from "./settings.js";
import { assert, make_pair_natural, Pair } from "./util.js";
Expand Down Expand Up @@ -116,17 +117,23 @@ export class AABBTree

// Find the best sibling for the new leaf
let bestSibling = this.root;
let bestCost = union(this.root.aabb, aabb).area;
let q: Pair<Node, number>[] = [{ p1: this.root, p2: 0.0 }];
let bestCost = union(this.root.aabb, aabb).perimeter;

while (q.length != 0)
// let q: Pair<Node, number>[] = [];

// Using priority queue
let q = new Heap<Pair<Node, number>>([], (a, b) => { return a.p2 < b.p2 });

q.push({ p1: this.root, p2: 0.0 });

while (q.length > 0)
{
let front: Pair<Node, number> = q.shift()!;
let front = q.pop()!;
let current = front.p1;
let inheritedCost = front.p2;

let combined = union(current.aabb, aabb);
let directCost = combined.area;
let directCost = combined.perimeter;

let costForCurrent = directCost + inheritedCost;
if (costForCurrent < bestCost)
Expand All @@ -135,9 +142,9 @@ export class AABBTree
bestSibling = current;
}

inheritedCost += directCost - current.aabb.area;
inheritedCost += directCost - current.aabb.perimeter;

let lowerBoundCost = aabb.area + inheritedCost;
let lowerBoundCost = aabb.perimeter + inheritedCost;
if (lowerBoundCost < bestCost)
{
if (!current.isLeaf)
Expand Down Expand Up @@ -257,16 +264,16 @@ export class AABBTree

if (child1.isLeaf == false)
{
let area1 = child1.aabb.area;
costDiffs[0] = union(child1.child1!.aabb, child2.aabb).area - area1;
costDiffs[1] = union(child1.child2!.aabb, child2.aabb).area - area1;
let area1 = child1.aabb.perimeter;
costDiffs[0] = union(child1.child1!.aabb, child2.aabb).perimeter - area1;
costDiffs[1] = union(child1.child2!.aabb, child2.aabb).perimeter - area1;
}

if (child2.isLeaf == false)
{
let area2 = child2.aabb.area;
costDiffs[2] = union(child2.child1!.aabb, child1.aabb).area - area2;
costDiffs[3] = union(child2.child2!.aabb, child1.aabb).area - area2;
let area2 = child2.aabb.perimeter;
costDiffs[2] = union(child2.child1!.aabb, child1.aabb).perimeter - area2;
costDiffs[3] = union(child2.child2!.aabb, child1.aabb).perimeter - area2;
}

let bestDiffIndex = 0;
Expand Down Expand Up @@ -406,11 +413,11 @@ export class AABBTree
return res;
}

let q = [this.root];
let stack = [this.root];

while (q.length != 0)
while (stack.length != 0)
{
let current = q.shift()!;
let current = stack.pop()!;

if (!current.aabb.testOverlap(region))
{
Expand All @@ -423,8 +430,8 @@ export class AABBTree
}
else
{
q.push(current.child1!);
q.push(current.child2!);
stack.push(current.child1!);
stack.push(current.child2!);
}
}

Expand Down Expand Up @@ -534,7 +541,7 @@ export class AABBTree

this.traverse(node =>
{
cost += node.aabb.area;
cost += node.aabb.perimeter;
});

return cost;
Expand Down
2 changes: 1 addition & 1 deletion src/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ export class Game

private updateCostLabel(): void
{
this.surfaceAreaLabel.innerHTML = "Tree surface area: " + this.tree.cost.toFixed(2);
this.surfaceAreaLabel.innerHTML = "Tree cost: " + this.tree.cost.toFixed(2);
}

render(r: Renderer): void
Expand Down
91 changes: 91 additions & 0 deletions src/heap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
export class Heap<T>
{
public heap: T[];
private compare;

constructor(array: T[], comparator: (a: T, b: T) => boolean)
{
this.heap = array;
this.compare = comparator;

this.heapify();
}

public push(value: T)
{
this.heap.push(value);

let index = this.heap.length - 1;
while (index > 0)
{
let parent = Math.trunc((index - 1) / 2);

if (this.compare(this.heap[parent], this.heap[index]))
{
break;
}

let tmp = this.heap[parent];
this.heap[parent] = this.heap[index];
this.heap[index] = tmp;

index = parent;
}
}

public pop(): T | undefined
{
let value = this.heap[0];

this.heap[0] = this.heap[this.heap.length - 1];
this.heap.pop();

this.downHeap(0);

return value;
}

private downHeap(index: number)
{
let size = this.length;

let largest = index;
let left = 2 * index + 1;
let right = 2 * index + 2;

if (left < size && this.compare(this.heap[left], this.heap[largest]))
{
largest = left;
}

if (right < size && this.compare(this.heap[right], this.heap[largest]))
{
largest = right;
}

if (largest != index)
{
// swap
let tmp = this.heap[index];
this.heap[index] = this.heap[largest];
this.heap[largest] = tmp;

// recurse down
this.downHeap(largest);
}
}

heapify()
{
// start from middle
for (let i = Math.trunc((this.length / 2) - 1); i >= 0; --i)
{
this.downHeap(i);
}
}

get length()
{
return this.heap.length;
}
}

0 comments on commit 5e3e7f8

Please sign in to comment.