summaryrefslogtreecommitdiff
path: root/city/geometry.lua
blob: a24e5081bf8c8c01520f249d6edd58570131c7e6 (plain)
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
local geom = {}

local function class(tbl)
   setmetatable(tbl, tbl)
   return tbl
end

-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
-- point
--
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

geom.point = class{
   __call = function(this, x, y)
      local pt = {}
      pt.x = x
      pt.y = y
      setmetatable(pt, {__index=this})
      return pt
   end,

   distance_to = function(self, pt)
      local d_x = self.x - pt.x
      local d_y = self.y - pt.y
      return math.sqrt(d_x*d_x + d_y*d_y)
   end,
}


-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
-- square
--
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

geom.square = class{
   __call = function(this, center, span)
      local square = {}
      square.center = center
      square.span = span
      square.x = this.axis_range(center.x, span/2)
      square.y = this.axis_range(center.y, span/2)
      setmetatable(square, {__index=this})
      return square
   end,

   axis_range = function(c, r) return {min=c-r, max=c+r} end,

   -- returns true if a point is inside of the square
   -- and false otherwise
   -- (x/y mininum exclusive, maximum inclusive)
   contains = function(self, point)
      local x_overlap =
	 (point.x > self.x.min) and (point.x <= self.x.max)
      local y_overlap =
	 (point.y > self.y.min) and (point.y <= self.y.max)

      if x_overlap and y_overlap then
	 return true
      end
      return false
   end,

   -- check if the square intersects the given square
   intersects = function(self, square)
      local overlap = function(a, b)
	 return math.max(a.min, b.min) < math.min(a.max, b.max)
      end
      local x_overlap = overlap(self.x, square.x)
      local y_overlap = overlap(self.y, square.y)

      if x_overlap and y_overlap then return true end
      return false
   end,

   -- return an array of four squares covering the same
   -- area as their parent
   divide = function(self)
      local this = getmetatable(self).__index

      local x = self.center.x
      local y = self.center.y
      local d = self.span / 4

      return {
	 this(geom.point(x-d, y-d), d*2),
	 this(geom.point(x+d, y-d), d*2),
	 this(geom.point(x-d, y+d), d*2),
	 this(geom.point(x+d, y+d), d*2),
      }
   end
}


-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
--
-- qt_node
--
-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

geom.qt_node = class{
   __call = function(this, center, span)
      local node = {}
      node.square = geom.square(center, span)
      setmetatable(node, {__index=this})
      return node
   end,

   is_leaf = function(self)
      if self.children then return false end
      return true
   end,

   insert = function(self, point)
      if not self.square:contains(point) then
	 -- point isn't even in this region
	 return false
      end

      if self:is_leaf() and not self.point then
	 -- no preexisting point, just store it
	 self.point = point
	 return true
      end

      if self:is_leaf() then
	 -- subdivide and re-insert original point
	 -- and then insert new point again
	 local self_point = self.point
	 self.point = nil
	 self.children = {}
	 local this = getmetatable(self).__index
	 local child_regions = self.square:divide()
	 for _, region in ipairs(child_regions) do
	    table.insert(
	       self.children,
	       this(region.center, region.span)
	    )
	 end
	 self:insert(self_point)
	 self:insert(point)
	 return true
      end

      -- not a leaf node
      for _, child in ipairs(self.children) do
	 if child:insert(point) then return true end
      end

      -- should never get here
      return false
   end
}

return geom