summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-02-25 13:53:04 -0600
committersanine <sanine.not@pm.me>2022-02-25 13:53:04 -0600
commit14927889ae16ae0f86eda45a1126d5d4e593cf91 (patch)
tree61899fdaa400f470f106305daee3eb5399cd33b3
parent9cd5a9f8ac5242cd220c9bae89432e817d548b83 (diff)
add quadtree with quadtree:get_closest()
-rw-r--r--city/geometry-test.lua58
-rw-r--r--city/geometry.lua50
2 files changed, 108 insertions, 0 deletions
diff --git a/city/geometry-test.lua b/city/geometry-test.lua
index 98275a3..28d55bc 100644
--- a/city/geometry-test.lua
+++ b/city/geometry-test.lua
@@ -360,3 +360,61 @@ test(
assert(n.point.y == 0.25)
end
)
+
+
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+--
+-- geom.quadtree
+--
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+test(
+ 'use quadtree to find closest points',
+ function()
+ local tree = geom.quadtree(w, h)
+
+ local N = 1000
+
+ -- insert N random points
+ local points = {}
+ for i=1,N do
+ local point = geom.point(math.random(), math.random())
+ table.insert(points, point)
+ tree:insert(point)
+ end
+
+ local find_closest = function(point)
+ local dist = math.huge
+ local closest
+ for _, pt in ipairs(points) do
+ if point:distance_to(pt) < dist then
+ dist = point:distance_to(pt)
+ closest = pt
+ end
+ end
+ return closest
+ end
+
+ -- check closest for N random points
+ for i=1,N do
+ local point = geom.point(math.random(), math.random())
+ local closest_bf = find_closest(point)
+ local closest_qt = tree:get_closest(point)
+
+ assert(closest_bf.x == closest_qt.x,
+ string.format(
+ 'bf: (%0.3f, %0.3f), qt: (%0.3f, %0.3f)',
+ closest_bf.x, closest_bf.y,
+ closest_qt.x, closest_qt.y
+ )
+ )
+ assert(closest_bf.y == closest_qt.y,
+ string.format(
+ 'bf: (%0.3f, %0.3f), qt: (%0.3f, %0.3f)',
+ closest_bf.x, closest_bf.y,
+ closest_qt.x, closest_qt.y
+ )
+ )
+ end
+ end
+)
diff --git a/city/geometry.lua b/city/geometry.lua
index 410b4a3..b26779b 100644
--- a/city/geometry.lua
+++ b/city/geometry.lua
@@ -186,4 +186,54 @@ geom.qt_node = class{
end
}
+
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+--
+-- quadtree
+--
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+geom.quadtree = class{
+ __call = function(this)
+ local tree = {}
+ tree.root = geom.qt_node(geom.point(0.5, 0.5), 1)
+
+ setmetatable(tree, {__index=this})
+ return tree
+ end,
+
+ insert = function(self, point)
+ self.root:insert(point)
+ end,
+
+ get_closest = function(self, point, accept_self)
+ local accept_self = accept_self or true
+
+ local region = self.root:find_containing(point)
+ local neighborhood = geom.square(point, region.square.span*4)
+
+ local near_points = {}
+ self.root:query(neighborhood, near_points)
+
+ if #near_points == 0 then return nil end
+ table.sort(near_points,
+ function(a, b)
+ return point:distance_to(a) < point:distance_to(b)
+ end
+ )
+
+ local result
+
+ if not accept_self and
+ (point.x == near_points[1].x) and
+ (point.y == near_points[1].y) then
+ result = near_points[2]
+ else
+ result = near_points[1]
+ end
+
+ return result
+ end
+}
+
return geom