From 7997a5faeab6885616abb3fdc90e3ced1413753b Mon Sep 17 00:00:00 2001 From: Tnze Date: Fri, 18 Mar 2022 12:37:33 +0800 Subject: [PATCH] BVH implement with less generic parm --- server/internal/bvh/bvh.go | 424 ++++++++++++++++---------------- server/internal/bvh/bvh_test.go | 288 +++++++++++----------- 2 files changed, 357 insertions(+), 355 deletions(-) diff --git a/server/internal/bvh/bvh.go b/server/internal/bvh/bvh.go index eb7d396..0f436e1 100644 --- a/server/internal/bvh/bvh.go +++ b/server/internal/bvh/bvh.go @@ -1,211 +1,217 @@ package bvh -// -//import ( -// "container/heap" -// "fmt" -// "golang.org/x/exp/constraints" -//) -// -//type Node[Vec constraints.Signed | constraints.Float, B interface { -// WithIn(Vec) bool -// Union(B) B -// Surface() float64 -//}, V any] struct { -// box B -// Value V -// parent *Node[Vec, B, V] -// children [2]*Node[Vec, B, V] -// isLeaf bool -//} -// -//func (n *Node[Vec, B, V]) findAnotherChild(not *Node[Vec, B, V]) *Node[Vec, B, V] { -// if n.children[0] == not { -// return n.children[1] -// } else if n.children[1] == not { -// return n.children[0] -// } -// panic("unreachable, please make sure the 'not' is the n's child") -//} -// -//func (n *Node[Vec, B, V]) findChildPointer(child *Node[Vec, B, V]) **Node[Vec, B, V] { -// if n.children[0] == child { -// return &n.children[0] -// } else if n.children[1] == child { -// return &n.children[1] -// } -// panic("unreachable, please make sure the 'not' is the n's child") -//} -// -//type Tree[I constraints.Signed | constraints.Float, B interface { -// Union(B) B -// Surface() I -//}, V any] struct { -// root *Node[I, B, V] -//} -// -//func (t *Tree[Vec, B, V]) Insert(leaf B, value V) (n *Node[Vec, B, V]) { -// n = &Node[Vec, B, V]{ -// box: leaf, -// Value: value, -// parent: nil, -// children: [2]*Node[Vec, B, V]{}, -// isLeaf: true, -// } -// if t.root == nil { -// t.root = n -// return -// } -// -// // Stage 1: find the best sibling for the new leaf -// sibling := t.root -// bestCost := t.root.box.Union(leaf).Surface() -// parentTo := &t.root // the parent's children pointer which point to the sibling -// queue := searchHeap[Node[Vec, B, V]]{searchItem[Node[Vec, B, V]]{pointer: t.root, parentTo: &t.root}} -// -// leafCost := leaf.Surface() -// for queue.Len() > 0 { -// p := heap.Pop(&queue).(searchItem[Node[Vec, B, V]]) -// // determine if node p has the best cost -// mergeSurface := p.pointer.box.Union(leaf).Surface() -// deltaCost := mergeSurface - p.pointer.box.Surface() -// cost := p.inheritedCost + mergeSurface -// if cost <= bestCost { -// bestCost = cost -// sibling = p.pointer -// parentTo = p.parentTo -// } -// // determine if it is worthwhile to explore the children of node p. -// inheritedCost := p.inheritedCost + deltaCost // lower bound -// if !p.pointer.isLeaf && inheritedCost+leafCost < bestCost { -// heap.Push(&queue, searchItem[Node[Vec, B, V]]{ -// pointer: p.pointer.children[0], -// parentTo: &p.pointer.children[0], -// inheritedCost: inheritedCost, -// }) -// heap.Push(&queue, searchItem[Node[Vec, B, V]]{ -// pointer: p.pointer.children[1], -// parentTo: &p.pointer.children[1], -// inheritedCost: inheritedCost, -// }) -// } -// } -// -// // Stage 2: create a new parent -// *parentTo = &Node[Vec, B, V]{ -// box: sibling.box.Union(leaf), // we will calculate in Stage3 -// parent: sibling.parent, -// children: [2]*Node[Vec, B, V]{sibling, n}, -// isLeaf: false, -// } -// n.parent = *parentTo -// sibling.parent = *parentTo -// -// // Stage 3: walk back up the tree refitting AABBs -// for p := *parentTo; p != nil; p = p.parent { -// p.box = p.children[0].box.Union(p.children[1].box) -// t.rotate(p) -// } -// return -//} -// -//func (t *Tree[Vec, B, V]) Delete(n *Node[Vec, B, V]) interface{} { -// if n.parent == nil { -// // n is the root -// t.root = nil -// return n.Value -// } -// sibling := n.parent.findAnotherChild(n) -// grand := n.parent.parent -// if grand == nil { -// // n's parent is root -// t.root = sibling -// sibling.parent = nil -// } else { -// p := grand.findChildPointer(n.parent) -// *p = sibling -// sibling.parent = grand -// for p := sibling.parent; p.parent != nil; p = p.parent { -// p.box = p.children[0].box.Union(p.children[1].box) -// t.rotate(p) -// } -// } -// return n.Value -//} -// -//func (t *Tree[Vec, B, V]) rotate(n *Node[Vec, B, V]) { -// if n.isLeaf || n.parent == nil { -// return -// } -// // trying to swap n's sibling and children -// sibling := n.parent.findAnotherChild(n) -// current := n.box.Surface() -// if n.children[1].box.Union(sibling.box).Surface() < current { -// // swap n.children[0] and sibling -// n.parent.children, n.children, n.children[0].parent, sibling.parent = [2]*Node[Vec, B, V]{n, n.children[0]}, [2]*Node[Vec, B, V]{sibling, n.children[1]}, n.parent, n -// n.box = n.children[0].box.Union(n.children[1].box) -// } else if n.children[0].box.Union(sibling.box).Surface() < current { -// // swap n.children[1] and sibling -// n.parent.children, n.children, n.children[1].parent, sibling.parent = [2]*Node[Vec, B, V]{n, n.children[1]}, [2]*Node[Vec, B, V]{sibling, n.children[0]}, n.parent, n -// n.box = n.children[0].box.Union(n.children[1].box) -// } -//} -// -////func lookupPoint[B interface { -//// Union(B) B -//// Surface() float64 -////}, V any](n *Node[B, V], point Vec2, f func(v V)) { -//// if n == nil || !n.box.WithIn(point) { -//// return -//// } -//// if n.isLeaf { -//// f(n.Value) -//// } else { -//// lookupVec(n.children[0], point, f) -//// lookupVec(n.children[1], point, f) -//// } -////} -// -//// -////func lookupAABB(n *Node, aabb AABB, f func(v interface{})) { -//// if n == nil || !n.box.Touch(aabb) { -//// return -//// } -//// if n.isLeaf { -//// f(n.Value) -//// } else { -//// lookupAABB(n.children[0], aabb, f) -//// lookupAABB(n.children[1], aabb, f) -//// } -////} -// -//type searchHeap[V any] []searchItem[V] -//type searchItem[V any] struct { -// pointer *V -// parentTo **V -// inheritedCost float64 -//} -// -//func (h searchHeap[V]) Len() int { return len(h) } -//func (h searchHeap[V]) Less(i, j int) bool { return h[i].inheritedCost < h[j].inheritedCost } -//func (h searchHeap[V]) Swap(i, j int) { h[i], h[j] = h[j], h[i] } -//func (h *searchHeap[V]) Push(x interface{}) { *h = append(*h, x.(searchItem[V])) } -//func (h *searchHeap[V]) Pop() interface{} { -// old := *h -// n := len(old) -// x := old[n-1] -// *h = old[0 : n-1] -// return x -//} -// -//func (t Tree[Vec, B, V]) String() string { -// return t.root.String() -//} -// -//func (n *Node[Vec, B, V]) String() string { -// if n.isLeaf { -// return fmt.Sprint(n.Value) -// } else { -// return fmt.Sprintf("{%v, %v}", n.children[0], n.children[1]) -// } -//} +import ( + "container/heap" + "fmt" + "golang.org/x/exp/constraints" +) + +type Node[I constraints.Float, B interface { + Union(B) B + Surface() I +}, V any] struct { + box B + Value V + parent *Node[I, B, V] + children [2]*Node[I, B, V] + isLeaf bool +} + +func (n *Node[I, B, V]) findAnotherChild(not *Node[I, B, V]) *Node[I, B, V] { + if n.children[0] == not { + return n.children[1] + } else if n.children[1] == not { + return n.children[0] + } + panic("unreachable, please make sure the 'not' is the n's child") +} + +func (n *Node[I, B, V]) findChildPointer(child *Node[I, B, V]) **Node[I, B, V] { + if n.children[0] == child { + return &n.children[0] + } else if n.children[1] == child { + return &n.children[1] + } + panic("unreachable, please make sure the 'not' is the n's child") +} + +func (n *Node[I, B, V]) each(test func(bound B) bool, foreach func(v V)) { + if n == nil { + return + } + if n.isLeaf { + if test(n.box) { + foreach(n.Value) + } + } else { + n.children[0].each(test, foreach) + n.children[1].each(test, foreach) + } +} + +type Tree[I constraints.Float, B interface { + Union(B) B + Surface() I +}, V any] struct { + root *Node[I, B, V] +} + +func (t *Tree[I, B, V]) Insert(leaf B, value V) (n *Node[I, B, V]) { + n = &Node[I, B, V]{ + box: leaf, + Value: value, + parent: nil, + children: [2]*Node[I, B, V]{nil, nil}, + isLeaf: true, + } + if t.root == nil { + t.root = n + return + } + + // Stage 1: find the best sibling for the new leaf + sibling := t.root + bestCost := t.root.box.Union(leaf).Surface() + parentTo := &t.root // the parent's children pointer which point to the sibling + + var queue searchHeap[I, Node[I, B, V]] + queue.Push(searchItem[I, Node[I, B, V]]{pointer: t.root, parentTo: &t.root}) + + leafCost := leaf.Surface() + for queue.Len() > 0 { + p := heap.Pop(&queue).(searchItem[I, Node[I, B, V]]) + // determine if node p has the best cost + mergeSurface := p.pointer.box.Union(leaf).Surface() + deltaCost := mergeSurface - p.pointer.box.Surface() + cost := p.inheritedCost + mergeSurface + if cost <= bestCost { + bestCost = cost + sibling = p.pointer + parentTo = p.parentTo + } + // determine if it is worthwhile to explore the children of node p. + inheritedCost := p.inheritedCost + deltaCost // lower bound + if !p.pointer.isLeaf && inheritedCost+leafCost < bestCost { + heap.Push(&queue, searchItem[I, Node[I, B, V]]{ + pointer: p.pointer.children[0], + parentTo: &p.pointer.children[0], + inheritedCost: inheritedCost, + }) + heap.Push(&queue, searchItem[I, Node[I, B, V]]{ + pointer: p.pointer.children[1], + parentTo: &p.pointer.children[1], + inheritedCost: inheritedCost, + }) + } + } + + // Stage 2: create a new parent + *parentTo = &Node[I, B, V]{ + box: sibling.box.Union(leaf), // we will calculate in Stage3 + parent: sibling.parent, + children: [2]*Node[I, B, V]{sibling, n}, + isLeaf: false, + } + n.parent = *parentTo + sibling.parent = *parentTo + + // Stage 3: walk back up the tree refitting AABBs + for p := *parentTo; p != nil; p = p.parent { + p.box = p.children[0].box.Union(p.children[1].box) + t.rotate(p) + } + return +} + +func (t *Tree[I, B, V]) Delete(n *Node[I, B, V]) interface{} { + if n.parent == nil { + // n is the root + t.root = nil + return n.Value + } + sibling := n.parent.findAnotherChild(n) + grand := n.parent.parent + if grand == nil { + // n's parent is root + t.root = sibling + sibling.parent = nil + } else { + p := grand.findChildPointer(n.parent) + *p = sibling + sibling.parent = grand + for p := sibling.parent; p.parent != nil; p = p.parent { + p.box = p.children[0].box.Union(p.children[1].box) + t.rotate(p) + } + } + return n.Value +} + +func (t *Tree[I, B, V]) rotate(n *Node[I, B, V]) { + if n.isLeaf || n.parent == nil { + return + } + // trying to swap n's sibling and children + sibling := n.parent.findAnotherChild(n) + current := n.box.Surface() + if n.children[1].box.Union(sibling.box).Surface() < current { + // swap n.children[0] and sibling + t1 := [2]*Node[I, B, V]{n, n.children[0]} + t2 := [2]*Node[I, B, V]{sibling, n.children[1]} + n.parent.children, n.children, n.children[0].parent, sibling.parent = t1, t2, n.parent, n + n.box = n.children[0].box.Union(n.children[1].box) + } else if n.children[0].box.Union(sibling.box).Surface() < current { + // swap n.children[1] and sibling + t1 := [2]*Node[I, B, V]{n, n.children[1]} + t2 := [2]*Node[I, B, V]{sibling, n.children[0]} + n.parent.children, n.children, n.children[1].parent, sibling.parent = t1, t2, n.parent, n + n.box = n.children[0].box.Union(n.children[1].box) + } +} + +func (t *Tree[I, B, V]) Find(test func(bound B) bool, foreach func(v V)) { + t.root.each(test, foreach) +} + +func (t Tree[I, B, V]) String() string { + return t.root.String() +} + +func (n *Node[I, B, V]) String() string { + if n.isLeaf { + return fmt.Sprint(n.Value) + } else { + return fmt.Sprintf("{%v, %v}", n.children[0], n.children[1]) + } +} + +func TouchPoint[Vec any, B interface{ WithIn(Vec) bool }](point Vec) func(bound B) bool { + return func(bound B) bool { + return bound.WithIn(point) + } +} + +func TouchBound[Vec any, B interface{ Touch(B) bool }](other B) func(bound B) bool { + return func(bound B) bool { + return bound.Touch(other) + } +} + +type searchHeap[I constraints.Float, V any] []searchItem[I, V] +type searchItem[I constraints.Float, V any] struct { + pointer *V + parentTo **V + inheritedCost I +} + +func (h searchHeap[I, V]) Len() int { return len(h) } +func (h searchHeap[I, V]) Less(i, j int) bool { return h[i].inheritedCost < h[j].inheritedCost } +func (h searchHeap[I, V]) Swap(i, j int) { h[i], h[j] = h[j], h[i] } +func (h *searchHeap[I, V]) Push(x interface{}) { *h = append(*h, x.(searchItem[I, V])) } +func (h *searchHeap[I, V]) Pop() interface{} { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} diff --git a/server/internal/bvh/bvh_test.go b/server/internal/bvh/bvh_test.go index 5193237..93d5a5b 100644 --- a/server/internal/bvh/bvh_test.go +++ b/server/internal/bvh/bvh_test.go @@ -1,148 +1,144 @@ package bvh -// -//import ( -// "math/rand" -// "testing" -//) -// -//func TestTree2_Insert(t *testing.T) { -// aabbs := []AABB[Vec2[float64]]{ -// {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{0, 0}}, -// {Upper: Vec2[float64]{2, 1}, Lower: Vec2[float64]{1, 0}}, -// {Upper: Vec2[float64]{11, 1}, Lower: Vec2[float64]{10, 0}}, -// {Upper: Vec2[float64]{12, 1}, Lower: Vec2[float64]{11, 0}}, -// {Upper: Vec2[float64]{101, 1}, Lower: Vec2[float64]{100, 0}}, -// {Upper: Vec2[float64]{102, 1}, Lower: Vec2[float64]{101, 0}}, -// {Upper: Vec2[float64]{111, 1}, Lower: Vec2[float64]{110, 0}}, -// {Upper: Vec2[float64]{112, 1}, Lower: Vec2[float64]{111, 0}}, -// {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{-1, -1}}, -// } -// var bvh Tree[float64, AABB[Vec2[float64]], int] -// for i, aabb := range aabbs { -// bvh.Insert(aabb, i) -// // visualize -// t.Log(bvh) -// } -// //bvh.FindVec(Vec2{0.5, 0.5}, func(v interface{}) { -// // t.Logf("find! %v", v) -// //}) -//} -// -//func TestTree2_FindVec(t *testing.T) { -// aabbs := []AABB[Vec2[float64]]{ -// {Upper: Vec2[float64]{2, 2}, Lower: Vec2[float64]{-1, -1}}, -// {Upper: Vec2[float64]{2, 1}, Lower: Vec2[float64]{-1, -2}}, -// {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{-2, -2}}, -// {Upper: Vec2[float64]{1, 2}, Lower: Vec2[float64]{-2, -1}}, -// } -// var bvh Tree[float64, AABB[Vec2[float64]], int] -// for i, aabb := range aabbs { -// bvh.Insert(aabb, i) -// // visualize -// t.Log(bvh) -// } -// //findVec := func(vec Vec2) (list []interface{}) { -// // bvh.FindVec(vec, func(v interface{}) { list = append(list, v) }) -// // return -// //} -// //t.Log(findVec(Vec2{0, 0})) -// //t.Log(findVec(Vec2{1.5, 0})) -// //t.Log(findVec(Vec2{1.5, 1.5})) -// //t.Log(findVec(Vec2{-1.5, 0})) -// // -// //findAABB := func(aabb AABB2) (list []interface{}) { -// // bvh.FindAABB(aabb, func(v interface{}) { list = append(list, v) }) -// // return -// //} -// //t.Log(findAABB(AABB2{Upper: Vec2{1, 1}, Lower: Vec2{-1, -1}})) -// //t.Log(findAABB(AABB2{Upper: Vec2{3, 3}, Lower: Vec2{1.5, 1.5}})) -// //t.Log(findAABB(AABB2{Upper: Vec2{-1.5, 0.5}, Lower: Vec2{-2.5, -0.5}})) -//} -// -//func TestTree2_Insert_rotation(t *testing.T) { -// var bvh Tree[float64, AABB[Vec2[float64]], int] -// for i := 0; i < 5; i++ { -// bvh.Insert(AABB[Vec2[float64]]{ -// Upper: Vec2[float64]{float64(i), float64(i)}, -// Lower: Vec2[float64]{float64(0), float64(0)}, -// }, i) -// } -//} -// -//func BenchmarkTree2_Insert_random(b *testing.B) { -// const size = 25 -// // generate test cases -// aabbs := make([]AABB[Vec2[float64]], b.N) -// poses := make([]Vec2[float64], b.N) -// for i := range aabbs { -// poses[i] = Vec2[float64]{rand.Float64() * 1e4, rand.Float64() * 1e4} -// aabbs[i] = AABB[Vec2[float64]]{ -// Upper: Vec2[float64]{poses[i][0] + size, poses[i][0] + size}, -// Lower: Vec2[float64]{poses[i][0] - size, poses[i][0] - size}, -// } -// } -// b.ResetTimer() -// -// var bvh Tree[float64, AABB[Vec2[float64]], any] -// for _, v := range aabbs { -// bvh.Insert(v, nil) -// } -// //for _, v := range poses { -// // bvh.FindVec(v, func(interface{}) {}) -// //} -//} -// -//func BenchmarkTree2_Insert_sorted1(b *testing.B) { -// // generate test cases -// var bvh Tree[float64, AABB[Vec2[float64]], int] -// upper := Vec2[float64]{float64(b.N), float64(b.N)} -// for i := 0; i < b.N; i++ { -// bvh.Insert(AABB[Vec2[float64]]{ -// Upper: upper, -// Lower: Vec2[float64]{float64(i), float64(i)}, -// }, i) -// } -//} -// -//func BenchmarkTree2_Insert_sorted2(b *testing.B) { -// // generate test cases -// var bvh Tree[float64, AABB[Vec2[float64]], int] -// for i := 0; i < b.N; i++ { -// bvh.Insert(AABB[Vec2[float64]]{ -// Upper: Vec2[float64]{float64(i), float64(i)}, -// Lower: Vec2[float64]{0, 0}, -// }, i) -// } -//} -// -//func BenchmarkTree2_Delete_random(b *testing.B) { -// const size = 25 -// // generate test cases -// aabbs := make([]AABB[Vec2[float64]], b.N) -// poses := make([]Vec2[float64], b.N) -// nodes := make([]*Node[AABB[Vec2[float64]], any], b.N) -// for i := range aabbs { -// poses[i] = Vec2[float64]{rand.Float64() * 1e4, rand.Float64() * 1e4} -// aabbs[i] = AABB[Vec2[float64]]{ -// Upper: Vec2[float64]{poses[i][0] + size, poses[i][0] + size}, -// Lower: Vec2[float64]{poses[i][0] - size, poses[i][0] - size}, -// } -// } -// b.ResetTimer() -// -// var bvh Tree[float64, AABB[Vec2[float64]], any] -// for i, v := range aabbs { -// nodes[i] = bvh.Insert(v, nil) -// } -// -// b.StopTimer() -// rand.Shuffle(b.N, func(i, j int) { -// nodes[i], nodes[j] = nodes[j], nodes[i] -// }) -// b.StartTimer() -// -// for _, v := range nodes { -// bvh.Delete(v) -// } -//} +import ( + "math/rand" + "testing" +) + +func TestTree2_Insert(t *testing.T) { + aabbs := []AABB[float64, Vec2[float64]]{ + {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{0, 0}}, + {Upper: Vec2[float64]{2, 1}, Lower: Vec2[float64]{1, 0}}, + {Upper: Vec2[float64]{11, 1}, Lower: Vec2[float64]{10, 0}}, + {Upper: Vec2[float64]{12, 1}, Lower: Vec2[float64]{11, 0}}, + {Upper: Vec2[float64]{101, 1}, Lower: Vec2[float64]{100, 0}}, + {Upper: Vec2[float64]{102, 1}, Lower: Vec2[float64]{101, 0}}, + {Upper: Vec2[float64]{111, 1}, Lower: Vec2[float64]{110, 0}}, + {Upper: Vec2[float64]{112, 1}, Lower: Vec2[float64]{111, 0}}, + {Upper: Vec2[float64]{1, 1}, Lower: Vec2[float64]{-1, -1}}, + } + var bvh Tree[float64, AABB[float64, Vec2[float64]], int] + for i, aabb := range aabbs { + bvh.Insert(aabb, i) + // visualize + t.Log(bvh) + } + bvh.Find(TouchPoint[Vec2[float64], AABB[float64, Vec2[float64]]](Vec2[float64]{0.5, 0.5}), func(v int) { + t.Logf("find! %v", v) + }) +} + +func TestTree2_Find_vec(t *testing.T) { + type Vec2d = Vec2[float64] + type AABBVec2d = AABB[float64, Vec2d] + type TreeAABBVec2di = Tree[float64, AABBVec2d, int] + + aabbs := []AABBVec2d{ + {Upper: Vec2d{2, 2}, Lower: Vec2d{-1, -1}}, + {Upper: Vec2d{2, 1}, Lower: Vec2d{-1, -2}}, + {Upper: Vec2d{1, 1}, Lower: Vec2d{-2, -2}}, + {Upper: Vec2d{1, 2}, Lower: Vec2d{-2, -1}}, + } + var bvh TreeAABBVec2di + for i, aabb := range aabbs { + bvh.Insert(aabb, i) + t.Log(bvh) + } + find := func(test func(bound AABBVec2d) bool) []int { + var result []int + bvh.Find(test, func(i int) { + result = append(result, i) + }) + return result + } + t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{0, 0}))) + t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{1.5, 0}))) + t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{1.5, 1.5}))) + t.Log(find(TouchPoint[Vec2d, AABBVec2d](Vec2d{-1.5, 0}))) + + t.Log(find(TouchBound[Vec2d, AABBVec2d](AABBVec2d{Upper: Vec2d{1, 1}, Lower: Vec2d{-1, -1}}))) + t.Log(find(TouchBound[Vec2d, AABBVec2d](AABBVec2d{Upper: Vec2d{1, 1}, Lower: Vec2d{1.5, 1.5}}))) + t.Log(find(TouchBound[Vec2d, AABBVec2d](AABBVec2d{Upper: Vec2d{-1.5, 0.5}, Lower: Vec2d{-2.5, -0.5}}))) +} + +func BenchmarkTree_Insert(b *testing.B) { + type Vec2d = Vec2[float64] + type AABBVec2d = AABB[float64, Vec2d] + type TreeAABBVec2da = Tree[float64, AABBVec2d, any] + + const size = 25 + // generate test cases + aabbs := make([]AABBVec2d, b.N) + poses := make([]Vec2d, b.N) + for i := range aabbs { + poses[i] = Vec2d{rand.Float64() * 1e4, rand.Float64() * 1e4} + aabbs[i] = AABBVec2d{ + Upper: Vec2d{poses[i][0] + size, poses[i][0] + size}, + Lower: Vec2d{poses[i][0] - size, poses[i][0] - size}, + } + } + b.ResetTimer() + + var bvh TreeAABBVec2da + for _, v := range aabbs { + bvh.Insert(v, nil) + } +} + +func BenchmarkTree2_Find_random(b *testing.B) { + type Vec2d = Vec2[float64] + type AABBVec2d = AABB[float64, Vec2d] + type TreeAABBVec2da = Tree[float64, AABBVec2d, any] + + const size = 25 + // generate test cases + aabbs := make([]AABBVec2d, b.N) + poses := make([]Vec2d, b.N) + for i := range aabbs { + poses[i] = Vec2d{rand.Float64() * 1e4, rand.Float64() * 1e4} + aabbs[i] = AABBVec2d{ + Upper: Vec2d{poses[i][0] + size, poses[i][0] + size}, + Lower: Vec2d{poses[i][0] - size, poses[i][0] - size}, + } + } + var bvh TreeAABBVec2da + for _, v := range aabbs { + bvh.Insert(v, nil) + } + b.ResetTimer() + + for _, v := range poses { + bvh.Find(TouchPoint[Vec2d, AABBVec2d](v), func(v any) {}) + } +} + +func BenchmarkTree2_Delete_random(b *testing.B) { + const size = 25 + // generate test cases + aabbs := make([]AABB[float64, Vec2[float64]], b.N) + poses := make([]Vec2[float64], b.N) + nodes := make([]*Node[float64, AABB[float64, Vec2[float64]], any], b.N) + for i := range aabbs { + poses[i] = Vec2[float64]{rand.Float64() * 1e4, rand.Float64() * 1e4} + aabbs[i] = AABB[float64, Vec2[float64]]{ + Upper: Vec2[float64]{poses[i][0] + size, poses[i][0] + size}, + Lower: Vec2[float64]{poses[i][0] - size, poses[i][0] - size}, + } + } + b.ResetTimer() + + var bvh Tree[float64, AABB[float64, Vec2[float64]], any] + for i, v := range aabbs { + nodes[i] = bvh.Insert(v, nil) + } + + b.StopTimer() + rand.Shuffle(b.N, func(i, j int) { + nodes[i], nodes[j] = nodes[j], nodes[i] + }) + b.StartTimer() + + for _, v := range nodes { + bvh.Delete(v) + } +}