-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathshash.lua
196 lines (163 loc) · 4.58 KB
/
shash.lua
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
--
-- shash.lua
--
-- Copyright (c) 2017 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local shash = { _version = "0.1.1" }
shash.__index = shash
function shash.new(cellsize)
local self = setmetatable({}, shash)
cellsize = cellsize or 64
self.cellsize = cellsize
self.tablepool = {}
self.cells = {}
self.entities = {}
return self
end
local function coord_to_key(x, y)
return x + y * 1e7
end
local function cell_position(cellsize, x, y)
return math.floor(x / cellsize), math.floor(y / cellsize)
end
local function each_overlapping_cell(self, e, fn, ...)
local cellsize = self.cellsize
local sx, sy = cell_position(cellsize, e[1], e[2])
local ex, ey = cell_position(cellsize, e[3], e[4])
for y = sy, ey do
for x = sx, ex do
local idx = coord_to_key(x, y)
fn(self, idx, ...)
end
end
end
local function add_entity_to_cell(self, idx, e)
if not self.cells[idx] then
self.cells[idx] = { e }
else
table.insert(self.cells[idx], e)
end
end
local function remove_entity_from_cell(self, idx, e)
local t = self.cells[idx]
local n = #t
-- Only one entity? Remove entity from cell and remove cell
if n == 1 then
self.cells[idx] = nil
return
end
-- Find and swap-remove entity
for i, v in ipairs(t) do
if v == e then
t[i] = t[n]
t[n] = nil
return
end
end
end
function shash:add(obj, x, y, w, h)
-- Create entity. The table is used as an array as this offers a noticable
-- performance increase on LuaJIT; the indices are as follows:
-- [1] = left, [2] = top, [3] = right, [4] = bottom, [5] = object
local e = { x, y, x + w, y + h, obj }
-- Add to main entities table
self.entities[obj] = e
-- Add to cells
each_overlapping_cell(self, e, add_entity_to_cell, e)
end
function shash:remove(obj)
-- Get entity of obj
local e = self.entities[obj]
-- Remove from main entities table
self.entities[obj] = nil
-- Remove from cells
each_overlapping_cell(self, e, remove_entity_from_cell, e)
end
function shash:update(obj, x, y, w, h)
-- Get entity from obj
local e = self.entities[obj]
-- No width/height specified? Get width/height from existing bounding box
w = w or e[3] - e[1]
h = h or e[4] - e[2]
-- Check the entity has actually changed cell-position, if it hasn't we don't
-- need to touch the cells at all
local cellsize = self.cellsize
local ax1, ay1 = cell_position(cellsize, e[1], e[2])
local ax2, ay2 = cell_position(cellsize, e[3], e[4])
local bx1, by1 = cell_position(cellsize, x, y)
local bx2, by2 = cell_position(cellsize, x + w, y + h)
local dirty = ax1 ~= bx1 or ay1 ~= by1 or ax2 ~= bx2 or ay2 ~= by2
-- Remove from old cells
if dirty then
each_overlapping_cell(self, e, remove_entity_from_cell, e)
end
-- Update entity
e[1], e[2], e[3], e[4] = x, y, x + w, y + h
-- Add to new cells
if dirty then
each_overlapping_cell(self, e, add_entity_to_cell, e)
end
end
function shash:clear()
-- Clear all cells and entities
for k in pairs(self.cells) do
self.cells[k] = nil
end
for k in pairs(self.entities) do
self.entities[k] = nil
end
end
local function overlaps(e1, e2)
return e1[3] > e2[1] and e1[1] < e2[3] and e1[4] > e2[2] and e1[2] < e2[4]
end
local function each_overlapping_in_cell(self, idx, e, set, fn, ...)
local t = self.cells[idx]
if not t then
return
end
for i, v in ipairs(t) do
if e ~= v and overlaps(e, v) and not set[v] then
fn(v[5], ...)
set[v] = true
end
end
end
local function each_overlapping_entity(self, e, fn, ...)
-- Init set for keeping track of which entities have already been handled
local set = table.remove(self.tablepool) or {}
-- Do overlap checks
each_overlapping_cell(self, e, each_overlapping_in_cell, e, set, fn, ...)
-- Clear set and return to pool
for v in pairs(set) do
set[v] = nil
end
table.insert(self.tablepool, set)
end
function shash:each(x, y, w, h, fn, ...)
local e = self.entities[x]
if e then
-- Got object, use its entity
each_overlapping_entity(self, e, y, w, h, fn, ...)
else
-- Got bounding box, make temporary entity
each_overlapping_entity(self, { x, y, x + w, y + h }, fn, ...)
end
end
function shash:info(opt, ...)
if opt == "cells" or opt == "entities" then
local n = 0
for k in pairs(self[opt]) do
n = n + 1
end
return n
end
if opt == "cell" then
local t = self.cells[ coord_to_key(...) ]
return t and #t or 0
end
error( string.format("invalid opt '%s'", opt) )
end
return shash