Sorry to dredge up the thread again, but am I correct in that this would only split on the X and Y axes, and is therefore a K-D tree of only 2 dimensions?
I tried adding the third dimension by adding this method to change the split axis, rather than toggling back and forth on X and Y.
fn nextDimension dim = return 1 + (mod dim 3) as integer
I changed it in the appropriate places:
tree_node[4] = nextDimension potential_parent[4]
Also, in GetNearestNeighbor I send the axis directly:
fn GetNearestNeighbor nodeRoot target_pt axis = ...
and I increment it inside this function also, rather than toggle on state
GetNearestNeighbor nextBranch target_pt (nextDimension axis)
and, of course, use 1 for the initial call instead of a boolean:
fn FindNearestNeighbor pt = ( this.GetNearestNeighbor this.root pt 1 )
The end of result of all this is… not much noticeable performance improvement. ^^; I did see some gain on initialization (I actually expected the effect to happen on search, not initialization) but not enough to be conclusive. To test in three dimensions instead of two, I replaced the text object with a series of nested GeoSpheres attached into a single Editable Mesh. Here is the result:
– original
KDTree build. Time: 0.081sec. Mem: 548240L Points count:7210
OK
Lookup: Time: 0.025sec. Mem: 480L
Lookup: Time: 0.024sec. Mem: 128L
Lookup: Time: 0.009sec. Mem: 128L
Lookup: Time: 0.024sec. Mem: 128L
Lookup: Time: 0.007sec. Mem: 128L
Lookup: Time: 0.024sec. Mem: 128L
Lookup: Time: 0.006sec. Mem: 128L
Lookup: Time: 0.024sec. Mem: 128L
Lookup: Time: 0.006sec. Mem: 128L
Lookup: Time: 0.024sec. Mem: 128L
Lookup: Time: 0.006sec. Mem: 128L
Lookup: Time: 0.023sec. Mem: 128L
Lookup: Time: 0.007sec. Mem: 128L
Lookup: Time: 0.023sec. Mem: 128L
Lookup: Time: 0.006sec. Mem: 128L
Lookup: Time: 0.024sec. Mem: 128L
– 3d
KDTree build. Time: 0.063sec. Mem: 548440L Points count:7210
OK
Lookup: Time: 0.021sec. Mem: 356L
Lookup: Time: 0.026sec. Mem: 128L
Lookup: Time: 0.023sec. Mem: 128L
Lookup: Time: 0.027sec. Mem: 128L
Lookup: Time: 0.013sec. Mem: 128L
Lookup: Time: 0.026sec. Mem: 128L
Lookup: Time: 0.021sec. Mem: 128L
Lookup: Time: 0.026sec. Mem: 128L
Lookup: Time: 0.022sec. Mem: 128L
Lookup: Time: 0.024sec. Mem: 128L
Lookup: Time: 0.02sec. Mem: 128L
Lookup: Time: 0.026sec. Mem: 128L
Lookup: Time: 0.021sec. Mem: 128L
Lookup: Time: 0.025sec. Mem: 128L
Lookup: Time: 0.021sec. Mem: 128L
Lookup: Time: 0.026sec. Mem: 128L
Lookup: Time: 0.02sec. Mem: 128L
I don’t know if this adds much; I’m actually not sure why the 2D version posted above works seemingly as correctly and efficiently… but just putting it out there. Here’s the whole code with my changes:
(
fn nextDimension dim = return 1 + (mod dim 3) as integer
struct KDTree
(
root,
fn AppendKDTreeNode root tree_node =
(
potential_parent = root
active = true
while active do
(
if tree_node[3][potential_parent[4]] <= potential_parent[3][potential_parent[4]] then
(
if potential_parent[1] != undefined then
(
potential_parent = potential_parent[1]
)
else
(
potential_parent[1] = tree_node
tree_node[4] = nextDimension potential_parent[4]
active = false
)
)
else
(
if potential_parent[2] != undefined then
(
potential_parent = potential_parent[2]
)
else
(
potential_parent[2] = tree_node
tree_node[4] = nextDimension potential_parent[4]
active = false
)
)
)
),
fn CreateFromPoints points =
(
local divideByAxis = 1
local tree = KDTree root:#( undefined, undefined, points[1], divideByAxis )
local tree_root = tree.root
local append_tree_node = tree.AppendKDTreeNode
for i = 2 to Points.Count do append_tree_node tree_root #( undefined, undefined, points[i], 1 )
tree
),
fn Closest n0 n1 target_pt =
(
if n0 == undefined then n1 else if n1 == undefined then n0 else
(
if distance n0[3] target_pt < distance n1[3] target_pt then n0 else n1
)
),
fn GetNearestNeighbor nodeRoot target_pt axis = if nodeRoot != undefined do
(
local nextBranch, otherBranch, best
if target_pt[axis] < nodeRoot[3][axis] then
(
nextBranch = nodeRoot[1]
otherBranch = nodeRoot[2]
)
else
(
nextBranch = nodeRoot[2]
otherBranch = nodeRoot[1]
)
temp = GetNearestNeighbor nextBranch target_pt (nextDimension axis)
best = Closest temp nodeRoot target_pt
dist = target_pt[axis] - nodeRoot[3][axis]
if (distance target_pt best[3]) >= dist do
(
temp = GetNearestNeighbor otherBranch target_pt (nextDimension axis)
best = Closest temp best target_pt
)
best
),
fn FindNearestNeighbor pt = ( this.GetNearestNeighbor this.root pt 1 )
)
delete objects
gc()
txt = editable_mesh()
for i = 1 to 5 do (
local geo = GeoSphere()
geo.segs = 12
geo.radius = i * 5
addModifier geo (turn_to_mesh())
maxOps.collapseNode geo true
attach txt geo
)
maxOps.CollapseNode txt true
txt.xray = true
pts = for i=1 to txt.numverts collect getvert txt i
t1=timestamp();hf = heapfree
::kd = KDTree.CreateFromPoints pts
format "KDTree build. Time: %sec. Mem: % Points count:%\n" ((timestamp()-t1)/1000 as float) (hf-heapfree) pts.count
-- testing
delete helpers
::h1 = point centermarker:on cross:off wirecolor:red
::h2 = point pos:txt.center centermarker:off cross:true wirecolor:green isSelected:true
deleteAllChangeHandlers id:#xxx
when transform h2 changes id:#xxx val do
(
t1=timestamp();hf = heapfree
n = kd.FindNearestNeighbor ::h2.pos
format "Lookup: Time: %sec. Mem: %\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)
::h1.pos = [ n[3].x, n[3].y, n[3].z ]
)
select h2
)