-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathziptree.go
257 lines (232 loc) · 6.74 KB
/
ziptree.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
package pix
// This file implements a zip tree:
// https://arxiv.org/abs/1806.06726
import (
"math"
"math/rand"
)
type Handle uint32
const nilHandle = Handle(0)
type zipNode struct {
rankAndKey uint32 // 8-bit rank, then 24-bit morton code
left, right Handle // handles to the left and right children in the pool
}
type zipTree struct {
root Handle // handle to the root node
nodes []zipNode // pool of pre-allocated nodes
free []Handle // free list
rng *rand.Rand
}
func newZipTree(rng *rand.Rand) *zipTree {
nodes := make([]zipNode, 1, 250_000)
free := make([]Handle, 0, 100_000)
return &zipTree{nilHandle, nodes, free, rng}
}
func (t *zipTree) Insert(key MortonCode) {
handle := t.newZipNode(key)
t.root = t.InsertRec(t.root, handle)
}
func (t *zipTree) Delete(key MortonCode) {
t.root = t.DeleteRec(t.root, key)
}
func (t *zipTree) Reset() {
t.root = nilHandle
t.nodes = t.nodes[:1]
t.free = t.free[:0]
}
// take some bits from each of the r, g, b channels and use them to break rank ties.
// tarjan calls these "fractional ranks" and suggests their use to improve the balance of
// the tree, which are otherwise right-heavy rgbrgbrgbrgbrgbrgbrgbrgb
func (x zipNode) Rank() uint32 { return x.rankAndKey & 0b11111111000000000000000000111000 }
func (x zipNode) Key() MortonCode {
return MortonCode(x.rankAndKey & 0b00000000111111111111111111111111)
}
func (t *zipTree) newZipNode(key MortonCode) Handle {
rank := uint32(0)
for t.rng.Int63()&1 == 0 {
rank++
}
rankAndKey := rank<<24 | uint32(key)
handle := t.Get(rankAndKey)
return handle
}
func (t *zipTree) MinKey(x zipNode) MortonCode {
for x.left != nilHandle {
x = t.Node(x.left)
}
return x.Key()
}
func (t *zipTree) MaxKey(x zipNode) MortonCode {
for x.right != nilHandle {
x = t.Node(x.right)
}
return x.Key()
}
func (t *zipTree) InsertRec(hroot, hx Handle) Handle {
if hroot == nilHandle {
return hx
}
// since the pool's backing buffer remains unmodified during deletion, we can take this reference safely
x := t.NodeRef(hx)
root := t.NodeRef(hroot)
if x.Key() < root.Key() {
if t.InsertRec(root.left, hx) == hx {
if x.Rank() < root.Rank() {
root.left = hx
} else {
root.left = x.right
x.right = hroot
return hx
}
}
} else {
if t.InsertRec(root.right, hx) == hx {
if x.Rank() <= root.Rank() {
root.right = hx
} else {
root.right = x.left
x.left = hroot
return hx
}
}
}
return hroot
}
func (t *zipTree) DeleteRec(hroot Handle, key MortonCode) Handle {
// since the pool's backing buffer remains unmodified during deletion, we can take this reference safely
root := t.NodeRef(hroot)
if key == root.Key() {
t.Put(hroot)
return t.zip(root.left, root.right)
}
if key < root.Key() {
left := t.Node(root.left)
if key == left.Key() {
t.Put(root.left)
root.left = t.zip(left.left, left.right)
} else {
t.DeleteRec(root.left, key)
}
} else {
right := t.Node(root.right)
if key == right.Key() {
t.Put(root.right)
root.right = t.zip(right.left, right.right)
} else {
t.DeleteRec(root.right, key)
}
}
return hroot
}
func (t *zipTree) zip(hx, hy Handle) Handle {
if hx == nilHandle {
return hy
}
if hy == nilHandle {
return hx
}
x, y := t.Node(hx), t.Node(hy)
if x.Rank() < y.Rank() {
t.SetLeft(hy, t.zip(hx, y.left))
return hy
} else {
t.SetRight(hx, t.zip(x.right, hy))
return hx
}
}
func (t *zipTree) Node(handle Handle) zipNode {
if handle == nilHandle {
panic("Got a node with handle zero in Handle.Node()")
}
return t.nodes[handle]
}
func (t *zipTree) SetLeft(handle Handle, x Handle) { t.nodes[handle].left = x }
func (t *zipTree) SetRight(handle Handle, x Handle) { t.nodes[handle].right = x }
func (t *zipTree) NodeRef(handle Handle) *zipNode {
if handle == nilHandle {
panic("Got a node with handle zero in Handle.NodeRef()")
}
return &t.nodes[handle]
}
// Put the handle back into the pool
func (t *zipTree) Put(handle Handle) {
t.free = append(t.free, handle)
}
// Get an unused handle from the pool
func (t *zipTree) Get(rankAndKey uint32) Handle {
n := len(t.free)
if n > 0 {
handle := t.free[n-1]
t.free = t.free[:n-1]
t.nodes[handle] = zipNode{rankAndKey, nilHandle, nilHandle}
return handle
}
handle := Handle(len(t.nodes))
t.nodes = append(t.nodes, zipNode{rankAndKey, nilHandle, nilHandle})
return handle
}
// Nearest-neighbor search in a 3D color space using an approach described in
// A Minimalist's Implementation of an Approximate Nearest Search in Fixed Dimensions:
// http://cs.uwaterloo.ca/~tmchan/sss.ps
//
// The algorithm is a variant of binary search through a Morton-ordered list of points
// which alternately prunes the search space in Euclidean space and along the curve.
// In our case we stores the points in a zip tree for dynamic updates, an perform the
// search by recursively traversing the tree.
func (t *zipTree) Nearest(q Color, qCode MortonCode) MortonCode {
var rSq uint32 = 1 << 30
var best MortonCode
var qPosCode, qNegCode MortonCode
// todo: figure out why epsilon can be set to eg. 100000 with no ill effect
// ε := float64(100000)
// approxFactor := (1.0 + ε) * (1.0 + ε)
// float64(distSqToBBox(qCode, t.MinKey(a), t.MaxKey(a), q))*approxFactor >= float64(rSq) {
var query func(q Color, ah Handle)
query = func(q Color, ah Handle) {
if ah == 0 {
return
}
a := t.Node(ah)
midCode := a.Key()
mid := mortonCodeToColor(midCode)
dSq := sqDist(q, mid)
if dSq < rSq {
rSq = dSq
var r uint8
if dSq >= 255*255 {
r = 255
} else {
r = uint8(math.Ceil(math.Sqrt(float64(dSq))))
}
qPosCode = mortonCode(satAdd(q.x, r), satAdd(q.y, r), satAdd(q.z, r))
qNegCode = mortonCode(satSub(q.x, r), satSub(q.y, r), satSub(q.z, r))
best = midCode
}
// a.left is only equal to a.right if both are nilHandle
// We exclude searching intervals if the distance from the query point to the snug power-of-2 bounding box
// enclosing the interval is farther away than our best distance so far.
if a.left == a.right || midCode == qCode || distSqToBBox(qCode, t.MinKey(a), t.MaxKey(a), q) >= rSq {
return
}
// If we can't exclude the interval, go ahead with a recursive search.
// Recurse into one or both halves of the array. We can avoid recursing
// into the second half when the box enclosing our "best radius" circle.
// The points qPos and qNeg are the smallest and largest morton codes of
// points within that box – thanks to properties of the z-order curve,
// any points below qNeg or above qPos are definitely more distant from q
// than the best radius r.
if qCode <= midCode {
query(q, a.left)
if qPosCode >= midCode {
query(q, a.right)
}
} else {
query(q, a.right)
if qNegCode <= midCode {
query(q, a.left)
}
}
}
query(q, t.root)
return best
}