mirror of
https://github.com/rxi/shash.git
synced 2024-11-09 19:21:47 +00:00
Initial commit
This commit is contained in:
commit
6a6a7dea85
20
LICENSE
Normal file
20
LICENSE
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2016 rxi
|
||||||
|
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
of the Software, and to permit persons to whom the Software is furnished to do
|
||||||
|
so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
42
README.md
Normal file
42
README.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# shash.lua
|
||||||
|
A simple, lightweight spatial hash for Lua.
|
||||||
|
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
#### shash.new([cellsize])
|
||||||
|
Creates a new spatial hash; if `cellsize` is not specified a default value of
|
||||||
|
`64` is used.
|
||||||
|
|
||||||
|
#### :add(obj, x, y, w, h)
|
||||||
|
Adds an object with the given bounding box to the spatial hash.
|
||||||
|
|
||||||
|
#### :update(obj, x, y [, w, h])
|
||||||
|
Updates the object's bounding box.
|
||||||
|
|
||||||
|
#### :remove(obj)
|
||||||
|
Removes the object from the spatial hash.
|
||||||
|
|
||||||
|
#### :clear()
|
||||||
|
Removes all objects from the spatial hash.
|
||||||
|
|
||||||
|
#### :each(x, y, w, h, fn, ...)
|
||||||
|
#### :each(obj, fn, ...)
|
||||||
|
For each object which overlaps with the given bounding box or object, the
|
||||||
|
function `fn` is called. The first argument passed to `fn` is the overlapping
|
||||||
|
object, followed by any additional arguments passed to `each()`.
|
||||||
|
|
||||||
|
#### :info(opt, ...)
|
||||||
|
Returns information about the spatial hash which can be useful for debugging.
|
||||||
|
Available options and their arguments are as follows:
|
||||||
|
|
||||||
|
Opt | Args | Description
|
||||||
|
------------|-----------|-------------------------------------------------------
|
||||||
|
`entities` | | Returns the total number of entities
|
||||||
|
`cells` | | Returns the total number of cells
|
||||||
|
`cell` | `x`, `y` | Returns the number of entities in the cell
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
This library is free software; you can redistribute it and/or modify it under
|
||||||
|
the terms of the MIT license. See [LICENSE](LICENSE) for details.
|
197
shash.lua
Normal file
197
shash.lua
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
--
|
||||||
|
-- shash.lua
|
||||||
|
--
|
||||||
|
-- Copyright (c) 2016 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.0" }
|
||||||
|
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 add_each_overlapping_in_cell(self, idx, e, set)
|
||||||
|
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) then
|
||||||
|
set[v] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function each_overlapping_entity(self, e, fn, ...)
|
||||||
|
-- Init set
|
||||||
|
local set = table.remove(self.tablepool) or {}
|
||||||
|
-- Do overlap checks and add overlapping to set
|
||||||
|
each_overlapping_cell(self, e, add_each_overlapping_in_cell, e, set)
|
||||||
|
-- Do callback for each entity in set and clear set
|
||||||
|
for v in pairs(set) do
|
||||||
|
fn(v[5], ...)
|
||||||
|
set[v] = nil
|
||||||
|
end
|
||||||
|
-- Return set to pool
|
||||||
|
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
|
Loading…
Reference in New Issue
Block a user