mirror of
https://github.com/rxi/shash.git
synced 2024-11-24 08:44:36 +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