feat: remove libs

This commit is contained in:
Kazhnuz 2021-11-25 10:58:06 +01:00
parent 66b77d59ed
commit bc8d61728b
18 changed files with 0 additions and 4687 deletions

View file

@ -1,687 +0,0 @@
-- binser.lua
--[[
Copyright (c) 2016 Calvin Rose
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.
]]
local assert = assert
local error = error
local select = select
local pairs = pairs
local getmetatable = getmetatable
local setmetatable = setmetatable
local tonumber = tonumber
local type = type
local loadstring = loadstring or load
local concat = table.concat
local char = string.char
local byte = string.byte
local format = string.format
local sub = string.sub
local dump = string.dump
local floor = math.floor
local frexp = math.frexp
local unpack = unpack or table.unpack
-- Lua 5.3 frexp polyfill
-- From https://github.com/excessive/cpml/blob/master/modules/utils.lua
if not frexp then
local log, abs, floor = math.log, math.abs, math.floor
local log2 = log(2)
frexp = function(x)
if x == 0 then return 0, 0 end
local e = floor(log(abs(x)) / log2 + 1)
return x / 2 ^ e, e
end
end
-- NIL = 202
-- FLOAT = 203
-- TRUE = 204
-- FALSE = 205
-- STRING = 206
-- TABLE = 207
-- REFERENCE = 208
-- CONSTRUCTOR = 209
-- FUNCTION = 210
-- RESOURCE = 211
-- INT64 = 212
local mts = {}
local ids = {}
local serializers = {}
local deserializers = {}
local resources = {}
local resources_by_name = {}
local function pack(...)
return {...}, select("#", ...)
end
local function not_array_index(x, len)
return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x)
end
local function type_check(x, tp, name)
assert(type(x) == tp,
format("Expected parameter %q to be of type %q.", name, tp))
end
local bigIntSupport = false
local isInteger
if math.type then -- Detect Lua 5.3
local mtype = math.type
bigIntSupport = loadstring[[
local char = string.char
return function(n)
local nn = n < 0 and -(n + 1) or n
local b1 = nn // 0x100000000000000
local b2 = nn // 0x1000000000000 % 0x100
local b3 = nn // 0x10000000000 % 0x100
local b4 = nn // 0x100000000 % 0x100
local b5 = nn // 0x1000000 % 0x100
local b6 = nn // 0x10000 % 0x100
local b7 = nn // 0x100 % 0x100
local b8 = nn % 0x100
if n < 0 then
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
end
return char(212, b1, b2, b3, b4, b5, b6, b7, b8)
end]]()
isInteger = function(x)
return mtype(x) == 'integer'
end
else
isInteger = function(x)
return floor(x) == x
end
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number serialization code modified from https://github.com/fperrad/lua-MessagePack
-- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer
local function number_to_str(n)
if isInteger(n) then -- int
if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data
return char(n + 27)
elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data
n = n + 8192
return char(128 + (floor(n / 0x100) % 0x100), n % 0x100)
elseif bigIntSupport then
return bigIntSupport(n)
end
end
local sign = 0
if n < 0.0 then
sign = 0x80
n = -n
end
local m, e = frexp(n) -- mantissa, exponent
if m ~= m then
return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
elseif m == 1/0 then
if sign == 0 then
return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
else
return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
end
end
e = e + 0x3FE
if e < 1 then -- denormalized numbers
m = m * 2 ^ (52 + e)
e = 0
else
m = (m * 2 - 1) * 2 ^ 52
end
return char(203,
sign + floor(e / 0x10),
(e % 0x10) * 0x10 + floor(m / 0x1000000000000),
floor(m / 0x10000000000) % 0x100,
floor(m / 0x100000000) % 0x100,
floor(m / 0x1000000) % 0x100,
floor(m / 0x10000) % 0x100,
floor(m / 0x100) % 0x100,
m % 0x100)
end
-- Copyright (C) 2012-2015 Francois Perrad.
-- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack
local function number_from_str(str, index)
local b = byte(str, index)
if b < 128 then
return b - 27, index + 1
elseif b < 192 then
return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2
end
local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8)
if b == 212 then
local flip = b1 >= 128
if flip then -- negative
b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4
b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8
end
local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
if flip then
return (-n) - 1, index + 9
else
return n, index + 9
end
end
local sign = b1 > 0x7F and -1 or 1
local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10)
local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8
local n
if e == 0 then
if m == 0 then
n = sign * 0.0
else
n = sign * (m / 2 ^ 52) * 2 ^ -1022
end
elseif e == 0x7FF then
if m == 0 then
n = sign * (1/0)
else
n = 0.0/0.0
end
else
n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF)
end
return n, index + 9
end
local types = {}
types["nil"] = function(x, visited, accum)
accum[#accum + 1] = "\202"
end
function types.number(x, visited, accum)
accum[#accum + 1] = number_to_str(x)
end
function types.boolean(x, visited, accum)
accum[#accum + 1] = x and "\204" or "\205"
end
function types.string(x, visited, accum)
local alen = #accum
if visited[x] then
accum[alen + 1] = "\208"
accum[alen + 2] = number_to_str(visited[x])
else
visited[x] = visited.next
visited.next = visited.next + 1
accum[alen + 1] = "\206"
accum[alen + 2] = number_to_str(#x)
accum[alen + 3] = x
end
end
local function check_custom_type(x, visited, accum)
local res = resources[x]
if res then
accum[#accum + 1] = "\211"
types[type(res)](res, visited, accum)
return true
end
local mt = getmetatable(x)
local id = mt and ids[mt]
if id then
if x == visited.temp then
error("Infinite loop in constructor.")
end
visited.temp = x
accum[#accum + 1] = "\209"
types[type(id)](id, visited, accum)
local args, len = pack(serializers[id](x))
accum[#accum + 1] = number_to_str(len)
for i = 1, len do
local arg = args[i]
types[type(arg)](arg, visited, accum)
end
visited[x] = visited.next
visited.next = visited.next + 1
return true
end
end
function types.userdata(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
error("Cannot serialize this userdata.")
end
end
function types.table(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited.next
visited.next = visited.next + 1
local xlen = #x
accum[#accum + 1] = "\207"
accum[#accum + 1] = number_to_str(xlen)
for i = 1, xlen do
local v = x[i]
types[type(v)](v, visited, accum)
end
local key_count = 0
for k in pairs(x) do
if not_array_index(k, xlen) then
key_count = key_count + 1
end
end
accum[#accum + 1] = number_to_str(key_count)
for k, v in pairs(x) do
if not_array_index(k, xlen) then
types[type(k)](k, visited, accum)
types[type(v)](v, visited, accum)
end
end
end
end
types["function"] = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, accum) then return end
visited[x] = visited.next
visited.next = visited.next + 1
local str = dump(x)
accum[#accum + 1] = "\210"
accum[#accum + 1] = number_to_str(#str)
accum[#accum + 1] = str
end
end
types.cdata = function(x, visited, accum)
if visited[x] then
accum[#accum + 1] = "\208"
accum[#accum + 1] = number_to_str(visited[x])
else
if check_custom_type(x, visited, #accum) then return end
error("Cannot serialize this cdata.")
end
end
types.thread = function() error("Cannot serialize threads.") end
local function deserialize_value(str, index, visited)
local t = byte(str, index)
if not t then return end
if t < 128 then
return t - 27, index + 1
elseif t < 192 then
return byte(str, index + 1) + 0x100 * (t - 128) - 8192, index + 2
elseif t == 202 then
return nil, index + 1
elseif t == 203 then
return number_from_str(str, index)
elseif t == 204 then
return true, index + 1
elseif t == 205 then
return false, index + 1
elseif t == 206 then
local length, dataindex = deserialize_value(str, index + 1, visited)
local nextindex = dataindex + length
local substr = sub(str, dataindex, nextindex - 1)
visited[#visited + 1] = substr
return substr, nextindex
elseif t == 207 then
local count, nextindex = number_from_str(str, index + 1)
local ret = {}
visited[#visited + 1] = ret
for i = 1, count do
ret[i], nextindex = deserialize_value(str, nextindex, visited)
end
count, nextindex = number_from_str(str, nextindex)
for i = 1, count do
local k, v
k, nextindex = deserialize_value(str, nextindex, visited)
v, nextindex = deserialize_value(str, nextindex, visited)
ret[k] = v
end
return ret, nextindex
elseif t == 208 then
local ref, nextindex = number_from_str(str, index + 1)
return visited[ref], nextindex
elseif t == 209 then
local count
local name, nextindex = deserialize_value(str, index + 1, visited)
count, nextindex = number_from_str(str, nextindex)
local args = {}
for i = 1, count do
args[i], nextindex = deserialize_value(str, nextindex, visited)
end
local ret = deserializers[name](unpack(args))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 210 then
local length, dataindex = deserialize_value(str, index + 1, visited)
local nextindex = dataindex + length
local ret = loadstring(sub(str, dataindex, nextindex - 1))
visited[#visited + 1] = ret
return ret, nextindex
elseif t == 211 then
local res, nextindex = deserialize_value(str, index + 1, visited)
return resources_by_name[res], nextindex
elseif t == 212 then
return number_from_str(str, index)
else
error("Could not deserialize type byte " .. t .. ".")
end
end
local function serialize(...)
local visited = {next = 1}
local accum = {}
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
return concat(accum)
end
local function make_file_writer(file)
return setmetatable({}, {
__newindex = function(_, _, v)
file:write(v)
end
})
end
local function serialize_to_file(path, mode, ...)
local file, err = io.open(path, mode)
assert(file, err)
local visited = {next = 1}
local accum = make_file_writer(file)
for i = 1, select("#", ...) do
local x = select(i, ...)
types[type(x)](x, visited, accum)
end
-- flush the writer
file:flush()
file:close()
end
local function writeFile(path, ...)
return serialize_to_file(path, "wb", ...)
end
local function appendFile(path, ...)
return serialize_to_file(path, "ab", ...)
end
local function deserialize(str, index)
assert(type(str) == "string", "Expected string to deserialize.")
local vals = {}
index = index or 1
local visited = {}
local len = 0
local val
while index do
val, index = deserialize_value(str, index, visited)
if index then
len = len + 1
vals[len] = val
end
end
return vals, len
end
local function deserializeN(str, n, index)
assert(type(str) == "string", "Expected string to deserialize.")
n = n or 1
assert(type(n) == "number", "Expected a number for parameter n.")
assert(n > 0 and floor(n) == n, "N must be a poitive integer.")
local vals = {}
index = index or 1
local visited = {}
local len = 0
local val
while index and len < n do
val, index = deserialize_value(str, index, visited)
if index then
len = len + 1
vals[len] = val
end
end
vals[len + 1] = index
return unpack(vals, 1, n + 1)
end
local function readFile(path)
local file, err = io.open(path, "rb")
assert(file, err)
local str = file:read("*all")
file:close()
return deserialize(str)
end
local function default_deserialize(metatable)
return function(...)
local ret = {}
for i = 1, select("#", ...), 2 do
ret[select(i, ...)] = select(i + 1, ...)
end
return setmetatable(ret, metatable)
end
end
local function default_serialize(x)
assert(type(x) == "table",
"Default serialization for custom types only works for tables.")
local args = {}
local len = 0
for k, v in pairs(x) do
args[len + 1], args[len + 2] = k, v
len = len + 2
end
return unpack(args, 1, len)
end
-- Templating
local function normalize_template(template)
local ret = {}
for i = 1, #template do
ret[i] = template[i]
end
local non_array_part = {}
-- The non-array part of the template (nested templates) have to be deterministic, so they are sorted.
-- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used
-- in templates. Looking for way around this.
for k in pairs(template) do
if not_array_index(k, #template) then
non_array_part[#non_array_part + 1] = k
end
end
table.sort(non_array_part)
for i = 1, #non_array_part do
local name = non_array_part[i]
ret[#ret + 1] = {name, normalize_template(template[name])}
end
return ret
end
local function templatepart_serialize(part, argaccum, x, len)
local extras = {}
local extracount = 0
for k, v in pairs(x) do
extras[k] = v
extracount = extracount + 1
end
for i = 1, #part do
extracount = extracount - 1
if type(part[i]) == "table" then
extras[part[i][1]] = nil
len = templatepart_serialize(part[i][2], argaccum, x[part[i][1]], len)
else
extras[part[i]] = nil
len = len + 1
argaccum[len] = x[part[i]]
end
end
if extracount > 0 then
argaccum[len + 1] = extras
else
argaccum[len + 1] = nil
end
return len + 1
end
local function templatepart_deserialize(ret, part, values, vindex)
for i = 1, #part do
local name = part[i]
if type(name) == "table" then
local newret = {}
ret[name[1]] = newret
vindex = templatepart_deserialize(newret, name[2], values, vindex)
else
ret[name] = values[vindex]
vindex = vindex + 1
end
end
local extras = values[vindex]
if extras then
for k, v in pairs(extras) do
ret[k] = v
end
end
return vindex + 1
end
local function template_serializer_and_deserializer(metatable, template)
return function(x)
argaccum = {}
local len = templatepart_serialize(template, argaccum, x, 0)
return unpack(argaccum, 1, len)
end, function(...)
local ret = {}
local len = select("#", ...)
local args = {...}
templatepart_deserialize(ret, template, args, 1)
return setmetatable(ret, metatable)
end
end
local function register(metatable, name, serialize, deserialize)
name = name or metatable.name
serialize = serialize or metatable._serialize
deserialize = deserialize or metatable._deserialize
if not serialize then
if metatable._template then
local t = normalize_template(metatable._template)
serialize, deserialize = template_serializer_and_deserializer(metatable, t)
elseif not deserialize then
serialize = default_serialize
deserialize = default_deserialize(metatable)
else
serialize = metatable
end
end
type_check(metatable, "table", "metatable")
type_check(name, "string", "name")
type_check(serialize, "function", "serialize")
type_check(deserialize, "function", "deserialize")
assert(not ids[metatable], "Metatable already registered.")
assert(not mts[name], ("Name %q already registered."):format(name))
mts[name] = metatable
ids[metatable] = name
serializers[name] = serialize
deserializers[name] = deserialize
return metatable
end
local function unregister(item)
local name, metatable
if type(item) == "string" then -- assume name
name, metatable = item, mts[item]
else -- assume metatable
name, metatable = ids[item], item
end
type_check(name, "string", "name")
type_check(metatable, "table", "metatable")
mts[name] = nil
ids[metatable] = nil
serializers[name] = nil
deserializers[name] = nil
return metatable
end
local function registerClass(class, name)
name = name or class.name
if class.__instanceDict then -- middleclass
register(class.__instanceDict, name)
else -- assume 30log or similar library
register(class, name)
end
return class
end
local function registerResource(resource, name)
type_check(name, "string", "name")
assert(not resources[resource],
"Resource already registered.")
assert(not resources_by_name[name],
format("Resource %q already exists.", name))
resources_by_name[name] = resource
resources[resource] = name
return resource
end
local function unregisterResource(name)
type_check(name, "string", "name")
assert(resources_by_name[name], format("Resource %q does not exist.", name))
local resource = resources_by_name[name]
resources_by_name[name] = nil
resources[resource] = nil
return resource
end
return {
-- aliases
s = serialize,
d = deserialize,
dn = deserializeN,
r = readFile,
w = writeFile,
a = appendFile,
serialize = serialize,
deserialize = deserialize,
deserializeN = deserializeN,
readFile = readFile,
writeFile = writeFile,
appendFile = appendFile,
register = register,
unregister = unregister,
registerResource = registerResource,
unregisterResource = unregisterResource,
registerClass = registerClass
}

View file

@ -1,68 +0,0 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
local Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({...}) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

View file

@ -1,99 +0,0 @@
--[[
CScreen v1.3 by CodeNMore
A simple way to make resolution-independent Love2D games
Tested for LOVE 0.10.1
See: https://github.com/CodeNMore/CScreen
Zlib License:
Copyright (c) 2016 CodeNMore
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in
a product, an acknowledgment in the product documentation would be appreciated
but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
--]]
local CScreen = {}
local rx, ry, ctr = 800, 600, true
local rxv, ryv, fsv, fsvr = 800, 600, 1.0, 1.0
local tx, ty, rwf, rhf = 0, 0, 800, 600
local cr, cg, cb, ca = 0, 0, 0, 255
-- Initializes CScreen with the initial size values
function CScreen.init(tw, th, cntr)
rx = tw or 800
ry = th or 600
ctr = cntr or false
CScreen.update(love.graphics.getWidth(), love.graphics.getHeight())
end
-- Draws letterbox borders
function CScreen.cease()
if ctr then
local pr, pg, pb, pa = love.graphics.getColor()
love.graphics.setColor(cr, cg, cb, ca)
love.graphics.scale(fsvr, fsvr)
if tx ~= 0 then
love.graphics.rectangle("fill", -tx, 0, tx, rhf)
love.graphics.rectangle("fill", rxv, 0, tx, rhf)
elseif ty ~= 0 then
love.graphics.rectangle("fill", 0, -ty, rwf, ty)
love.graphics.rectangle("fill", 0, ryv, rwf, ty)
end
love.graphics.setColor(pr, pg, pb, pa)
end
end
-- Scales and centers all graphics properly
function CScreen.apply()
if ctr then
love.graphics.translate(tx, ty)
end
love.graphics.scale(fsv, fsv)
end
-- Updates CScreen when the window size changes
function CScreen.update(w, h)
local sx = w / rx
local sy = h / ry
fsv = math.min(sx, sy)
fsvr = 1 / fsv
-- Centering
if ctr and fsv == sx then -- Vertically
tx = 0
ty = (h / 2) - (ry * fsv / 2)
elseif ctr and fsv == sy then -- Horizontally
ty = 0
tx = (w / 2) - (rx * fsv / 2)
end
-- Variable sets
rwf = w
rhf = h
rxv = rx * fsv
ryv = ry * fsv
end
-- Convert from window coordinates to target coordinates
function CScreen.project(x, y)
return math.floor((x - tx) / fsv), math.floor((y - ty) / fsv)
end
-- Change letterbox color
function CScreen.setColor(r, g, b, a)
cr = r
cg = g
cb = b
ca = a
end
-- Return the table for use
return CScreen

View file

@ -1,216 +0,0 @@
--[[
Copyright (c) 2010-2015 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local _PATH = (...):match('^(.*[%./])[^%.%/]+$') or ''
local cos, sin = math.cos, math.sin
local camera = {}
camera.__index = camera
-- Movement interpolators (for camera locking/windowing)
camera.smooth = {}
function camera.smooth.none()
return function(dx,dy) return dx,dy end
end
function camera.smooth.linear(speed)
assert(type(speed) == "number", "Invalid parameter: speed = "..tostring(speed))
return function(dx,dy, s)
-- normalize direction
local d = math.sqrt(dx*dx+dy*dy)
local dts = math.min((s or speed) * love.timer.getDelta(), d) -- prevent overshooting the goal
if d > 0 then
dx,dy = dx/d, dy/d
end
return dx*dts, dy*dts
end
end
function camera.smooth.damped(stiffness)
assert(type(stiffness) == "number", "Invalid parameter: stiffness = "..tostring(stiffness))
return function(dx,dy, s)
local dts = love.timer.getDelta() * (s or stiffness)
return dx*dts, dy*dts
end
end
local function new(x,y, zoom, rot, smoother)
x,y = x or love.graphics.getWidth()/2, y or love.graphics.getHeight()/2
zoom = zoom or 1
rot = rot or 0
smoother = smoother or camera.smooth.none() -- for locking, see below
return setmetatable({x = x, y = y, scale = zoom, rot = rot, smoother = smoother}, camera)
end
function camera:lookAt(x,y)
self.x, self.y = x, y
return self
end
function camera:move(dx,dy)
self.x, self.y = self.x + dx, self.y + dy
return self
end
function camera:position()
return self.x, self.y
end
function camera:rotate(phi)
self.rot = self.rot + phi
return self
end
function camera:rotateTo(phi)
self.rot = phi
return self
end
function camera:zoom(mul)
self.scale = self.scale * mul
return self
end
function camera:zoomTo(zoom)
self.scale = zoom
return self
end
function camera:attach(x,y,w,h, noclip)
x,y = x or 0, y or 0
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
self._sx,self._sy,self._sw,self._sh = love.graphics.getScissor()
if not noclip then
love.graphics.setScissor(x,y,w,h)
end
local cx,cy = x+w/2, y+h/2
love.graphics.push()
love.graphics.translate(cx, cy)
love.graphics.scale(self.scale)
love.graphics.rotate(self.rot)
love.graphics.translate(-self.x, -self.y)
end
function camera:detach()
love.graphics.pop()
love.graphics.setScissor(self._sx,self._sy,self._sw,self._sh)
end
function camera:draw(...)
local x,y,w,h,noclip,func
local nargs = select("#", ...)
if nargs == 1 then
func = ...
elseif nargs == 5 then
x,y,w,h,func = ...
elseif nargs == 6 then
x,y,w,h,noclip,func = ...
else
error("Invalid arguments to camera:draw()")
end
self:attach(x,y,w,h,noclip)
func()
self:detach()
end
-- world coordinates to camera coordinates
function camera:cameraCoords(x,y, ox,oy,w,h)
ox, oy = ox or 0, oy or 0
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
-- x,y = ((x,y) - (self.x, self.y)):rotated(self.rot) * self.scale + center
local c,s = cos(self.rot), sin(self.rot)
x,y = x - self.x, y - self.y
x,y = c*x - s*y, s*x + c*y
return x*self.scale + w/2 + ox, y*self.scale + h/2 + oy
end
-- camera coordinates to world coordinates
function camera:worldCoords(x,y, ox,oy,w,h)
ox, oy = ox or 0, oy or 0
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
-- x,y = (((x,y) - center) / self.scale):rotated(-self.rot) + (self.x,self.y)
local c,s = cos(-self.rot), sin(-self.rot)
x,y = (x - w/2 - ox) / self.scale, (y - h/2 - oy) / self.scale
x,y = c*x - s*y, s*x + c*y
return x+self.x, y+self.y
end
function camera:mousePosition(ox,oy,w,h)
local mx,my = love.mouse.getPosition()
return self:worldCoords(mx,my, ox,oy,w,h)
end
-- camera scrolling utilities
function camera:lockX(x, smoother, ...)
local dx, dy = (smoother or self.smoother)(x - self.x, self.y, ...)
self.x = self.x + dx
return self
end
function camera:lockY(y, smoother, ...)
local dx, dy = (smoother or self.smoother)(self.x, y - self.y, ...)
self.y = self.y + dy
return self
end
function camera:lockPosition(x,y, smoother, ...)
return self:move((smoother or self.smoother)(x - self.x, y - self.y, ...))
end
function camera:lockWindow(x, y, x_min, x_max, y_min, y_max, smoother, ...)
-- figure out displacement in camera coordinates
x,y = self:cameraCoords(x,y)
local dx, dy = 0,0
if x < x_min then
dx = x - x_min
elseif x > x_max then
dx = x - x_max
end
if y < y_min then
dy = y - y_min
elseif y > y_max then
dy = y - y_max
end
-- transform displacement to movement in world coordinates
local c,s = cos(-self.rot), sin(-self.rot)
dx,dy = (c*dx - s*dy) / self.scale, (s*dx + c*dy) / self.scale
-- move
self:move((smoother or self.smoother)(dx,dy,...))
end
-- the module
return setmetatable({new = new, smooth = camera.smooth},
{__call = function(_, ...) return new(...) end})

View file

@ -1,98 +0,0 @@
--[[
Copyright (c) 2010-2013 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local function include_helper(to, from, seen)
if from == nil then
return to
elseif type(from) ~= 'table' then
return from
elseif seen[from] then
return seen[from]
end
seen[from] = to
for k,v in pairs(from) do
k = include_helper({}, k, seen) -- keys might also be tables
if to[k] == nil then
to[k] = include_helper({}, v, seen)
end
end
return to
end
-- deeply copies `other' into `class'. keys in `other' that are already
-- defined in `class' are omitted
local function include(class, other)
return include_helper(class, other, {})
end
-- returns a deep copy of `other'
local function clone(other)
return setmetatable(include({}, other), getmetatable(other))
end
local function new(class)
-- mixins
class = class or {} -- class can be nil
local inc = class.__includes or {}
if getmetatable(inc) then inc = {inc} end
for _, other in ipairs(inc) do
if type(other) == "string" then
other = _G[other]
end
include(class, other)
end
-- class implementation
class.__index = class
class.init = class.init or class[1] or function() end
class.include = class.include or include
class.clone = class.clone or clone
-- constructor call
return setmetatable(class, {__call = function(c, ...)
local o = setmetatable({}, c)
o:init(...)
return o
end})
end
-- interface for cross class-system compatibility (see https://github.com/bartbes/Class-Commons).
if class_commons ~= false and not common then
common = {}
function common.class(name, prototype, parent)
return new{__includes = {prototype, parent}}
end
function common.instance(class, ...)
return class(...)
end
end
-- the module
return setmetatable({new = new, include = include, clone = clone},
{__call = function(_,...) return new(...) end})

View file

@ -1,108 +0,0 @@
--[[
Copyright (c) 2010-2013 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local function __NULL__() end
-- default gamestate produces error on every callback
local state_init = setmetatable({leave = __NULL__},
{__index = function() error("Gamestate not initialized. Use Gamestate.switch()") end})
local stack = {state_init}
local initialized_states = setmetatable({}, {__mode = "k"})
local state_is_dirty = true
local GS = {}
function GS.new(t) return t or {} end -- constructor - deprecated!
local function change_state(stack_offset, to, ...)
local pre = stack[#stack]
-- initialize only on first call
;(initialized_states[to] or to.init or __NULL__)(to)
initialized_states[to] = __NULL__
stack[#stack+stack_offset] = to
state_is_dirty = true
return (to.enter or __NULL__)(to, pre, ...)
end
function GS.switch(to, ...)
assert(to, "Missing argument: Gamestate to switch to")
assert(to ~= GS, "Can't call switch with colon operator")
;(stack[#stack].leave or __NULL__)(stack[#stack])
return change_state(0, to, ...)
end
function GS.push(to, ...)
assert(to, "Missing argument: Gamestate to switch to")
assert(to ~= GS, "Can't call push with colon operator")
return change_state(1, to, ...)
end
function GS.pop(...)
assert(#stack > 1, "No more states to pop!")
local pre, to = stack[#stack], stack[#stack-1]
stack[#stack] = nil
;(pre.leave or __NULL__)(pre)
state_is_dirty = true
return (to.resume or __NULL__)(to, pre, ...)
end
function GS.current()
return stack[#stack]
end
-- fetch event callbacks from love.handlers
local all_callbacks = { 'draw', 'errhand', 'update' }
for k in pairs(love.handlers) do
all_callbacks[#all_callbacks+1] = k
end
function GS.registerEvents(callbacks)
local registry = {}
callbacks = callbacks or all_callbacks
for _, f in ipairs(callbacks) do
registry[f] = love[f] or __NULL__
love[f] = function(...)
registry[f](...)
return GS[f](...)
end
end
end
-- forward any undefined functions
setmetatable(GS, {__index = function(_, func)
-- call function only if at least one 'update' was called beforehand
-- (see issue #46)
if not state_is_dirty or func == 'update' then
state_is_dirty = false
return function(...)
return (stack[#stack][func] or __NULL__)(stack[#stack], ...)
end
end
return __NULL__
end})
return GS

View file

@ -1,102 +0,0 @@
--[[
Copyright (c) 2012-2013 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local Registry = {}
Registry.__index = function(self, key)
return Registry[key] or (function()
local t = {}
rawset(self, key, t)
return t
end)()
end
function Registry:register(s, f)
self[s][f] = f
return f
end
function Registry:emit(s, ...)
for f in pairs(self[s]) do
f(...)
end
end
function Registry:remove(s, ...)
local f = {...}
for i = 1,select('#', ...) do
self[s][f[i]] = nil
end
end
function Registry:clear(...)
local s = {...}
for i = 1,select('#', ...) do
self[s[i]] = {}
end
end
function Registry:emitPattern(p, ...)
for s in pairs(self) do
if s:match(p) then self:emit(s, ...) end
end
end
function Registry:registerPattern(p, f)
for s in pairs(self) do
if s:match(p) then self:register(s, f) end
end
return f
end
function Registry:removePattern(p, ...)
for s in pairs(self) do
if s:match(p) then self:remove(s, ...) end
end
end
function Registry:clearPattern(p)
for s in pairs(self) do
if s:match(p) then self[s] = {} end
end
end
-- instancing
function Registry.new()
return setmetatable({}, Registry)
end
-- default instance
local default = Registry.new()
-- module forwards calls to default instance
local module = {}
for k in pairs(Registry) do
if k ~= "__index" then
module[k] = function(...) return default[k](default, ...) end
end
end
return setmetatable(module, {__call = Registry.new})

View file

@ -1,210 +0,0 @@
--[[
Copyright (c) 2010-2013 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local Timer = {}
Timer.__index = Timer
local function _nothing_() end
function Timer:update(dt)
local to_remove = {}
for handle in pairs(self.functions) do
-- handle: {
-- time = <number>,
-- after = <function>,
-- during = <function>,
-- limit = <number>,
-- count = <number>,
-- }
handle.time = handle.time + dt
handle.during(dt, math.max(handle.limit - handle.time, 0))
while handle.time >= handle.limit and handle.count > 0 do
if handle.after(handle.after) == false then
handle.count = 0
break
end
handle.time = handle.time - handle.limit
handle.count = handle.count - 1
end
if handle.count == 0 then
table.insert(to_remove, handle)
end
end
for i = 1, #to_remove do
self.functions[to_remove[i]] = nil
end
end
function Timer:during(delay, during, after)
local handle = { time = 0, during = during, after = after or _nothing_, limit = delay, count = 1 }
self.functions[handle] = true
return handle
end
function Timer:after(delay, func)
return self:during(delay, _nothing_, func)
end
function Timer:every(delay, after, count)
local count = count or math.huge -- exploit below: math.huge - 1 = math.huge
local handle = { time = 0, during = _nothing_, after = after, limit = delay, count = count }
self.functions[handle] = true
return handle
end
function Timer:cancel(handle)
self.functions[handle] = nil
end
function Timer:clear()
self.functions = {}
end
function Timer:script(f)
local co = coroutine.wrap(f)
co(function(t)
self:after(t, co)
coroutine.yield()
end)
end
Timer.tween = setmetatable({
-- helper functions
out = function(f) -- 'rotates' a function
return function(s, ...) return 1 - f(1-s, ...) end
end,
chain = function(f1, f2) -- concatenates two functions
return function(s, ...) return (s < .5 and f1(2*s, ...) or 1 + f2(2*s-1, ...)) * .5 end
end,
-- useful tweening functions
linear = function(s) return s end,
quad = function(s) return s*s end,
cubic = function(s) return s*s*s end,
quart = function(s) return s*s*s*s end,
quint = function(s) return s*s*s*s*s end,
sine = function(s) return 1-math.cos(s*math.pi/2) end,
expo = function(s) return 2^(10*(s-1)) end,
circ = function(s) return 1 - math.sqrt(1-s*s) end,
back = function(s,bounciness)
bounciness = bounciness or 1.70158
return s*s*((bounciness+1)*s - bounciness)
end,
bounce = function(s) -- magic numbers ahead
local a,b = 7.5625, 1/2.75
return math.min(a*s^2, a*(s-1.5*b)^2 + .75, a*(s-2.25*b)^2 + .9375, a*(s-2.625*b)^2 + .984375)
end,
elastic = function(s, amp, period)
amp, period = amp and math.max(1, amp) or 1, period or .3
return (-amp * math.sin(2*math.pi/period * (s-1) - math.asin(1/amp))) * 2^(10*(s-1))
end,
}, {
-- register new tween
__call = function(tween, self, len, subject, target, method, after, ...)
-- recursively collects fields that are defined in both subject and target into a flat list
local function tween_collect_payload(subject, target, out)
for k,v in pairs(target) do
local ref = subject[k]
assert(type(v) == type(ref), 'Type mismatch in field "'..k..'".')
if type(v) == 'table' then
tween_collect_payload(ref, v, out)
else
local ok, delta = pcall(function() return (v-ref)*1 end)
assert(ok, 'Field "'..k..'" does not support arithmetic operations')
out[#out+1] = {subject, k, delta}
end
end
return out
end
method = tween[method or 'linear'] -- see __index
local payload, t, args = tween_collect_payload(subject, target, {}), 0, {...}
local last_s = 0
return self:during(len, function(dt)
t = t + dt
local s = method(math.min(1, t/len), unpack(args))
local ds = s - last_s
last_s = s
for _, info in ipairs(payload) do
local ref, key, delta = unpack(info)
ref[key] = ref[key] + delta * ds
end
end, after)
end,
-- fetches function and generated compositions for method `key`
__index = function(tweens, key)
if type(key) == 'function' then return key end
assert(type(key) == 'string', 'Method must be function or string.')
if rawget(tweens, key) then return rawget(tweens, key) end
local function construct(pattern, f)
local method = rawget(tweens, key:match(pattern))
if method then return f(method) end
return nil
end
local out, chain = rawget(tweens,'out'), rawget(tweens,'chain')
return construct('^in%-([^-]+)$', function(...) return ... end)
or construct('^out%-([^-]+)$', out)
or construct('^in%-out%-([^-]+)$', function(f) return chain(f, out(f)) end)
or construct('^out%-in%-([^-]+)$', function(f) return chain(out(f), f) end)
or error('Unknown interpolation method: ' .. key)
end})
-- Timer instancing
function Timer.new()
return setmetatable({functions = {}, tween = Timer.tween}, Timer)
end
-- default instance
local default = Timer.new()
-- module forwards calls to default instance
local module = {}
for k in pairs(Timer) do
if k ~= "__index" then
module[k] = function(...) return default[k](default, ...) end
end
end
module.tween = setmetatable({}, {
__index = Timer.tween,
__newindex = function(k,v) Timer.tween[k] = v end,
__call = function(t, ...) return default:tween(...) end,
})
return setmetatable(module, {__call = Timer.new})

View file

@ -1,172 +0,0 @@
--[[
Copyright (c) 2012-2013 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2
local function str(x,y)
return "("..tonumber(x)..","..tonumber(y)..")"
end
local function mul(s, x,y)
return s*x, s*y
end
local function div(s, x,y)
return x/s, y/s
end
local function add(x1,y1, x2,y2)
return x1+x2, y1+y2
end
local function sub(x1,y1, x2,y2)
return x1-x2, y1-y2
end
local function permul(x1,y1, x2,y2)
return x1*x2, y1*y2
end
local function dot(x1,y1, x2,y2)
return x1*x2 + y1*y2
end
local function det(x1,y1, x2,y2)
return x1*y2 - y1*x2
end
local function eq(x1,y1, x2,y2)
return x1 == x2 and y1 == y2
end
local function lt(x1,y1, x2,y2)
return x1 < x2 or (x1 == x2 and y1 < y2)
end
local function le(x1,y1, x2,y2)
return x1 <= x2 and y1 <= y2
end
local function len2(x,y)
return x*x + y*y
end
local function len(x,y)
return sqrt(x*x + y*y)
end
local function fromPolar(angle, radius)
return cos(angle)*radius, sin(angle)*radius
end
local function toPolar(x, y)
return atan2(y,x), len(x,y)
end
local function dist2(x1,y1, x2,y2)
return len2(x1-x2, y1-y2)
end
local function dist(x1,y1, x2,y2)
return len(x1-x2, y1-y2)
end
local function normalize(x,y)
local l = len(x,y)
if l > 0 then
return x/l, y/l
end
return x,y
end
local function rotate(phi, x,y)
local c, s = cos(phi), sin(phi)
return c*x - s*y, s*x + c*y
end
local function perpendicular(x,y)
return -y, x
end
local function project(x,y, u,v)
local s = (x*u + y*v) / (u*u + v*v)
return s*u, s*v
end
local function mirror(x,y, u,v)
local s = 2 * (x*u + y*v) / (u*u + v*v)
return s*u - x, s*v - y
end
-- ref.: http://blog.signalsondisplay.com/?p=336
local function trim(maxLen, x, y)
local s = maxLen * maxLen / len2(x, y)
s = s > 1 and 1 or math.sqrt(s)
return x * s, y * s
end
local function angleTo(x,y, u,v)
if u and v then
return atan2(y, x) - atan2(v, u)
end
return atan2(y, x)
end
-- the module
return {
str = str,
fromPolar = fromPolar,
toPolar = toPolar,
-- arithmetic
mul = mul,
div = div,
add = add,
sub = sub,
permul = permul,
dot = dot,
det = det,
cross = det,
-- relation
eq = eq,
lt = lt,
le = le,
-- misc operations
len2 = len2,
len = len,
dist2 = dist2,
dist = dist,
normalize = normalize,
rotate = rotate,
perpendicular = perpendicular,
project = project,
mirror = mirror,
trim = trim,
angleTo = angleTo,
}

View file

@ -1,199 +0,0 @@
--[[
Copyright (c) 2010-2013 Matthias Richter
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.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
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.
]]--
local assert = assert
local sqrt, cos, sin, atan2 = math.sqrt, math.cos, math.sin, math.atan2
local vector = {}
vector.__index = vector
local function new(x,y)
return setmetatable({x = x or 0, y = y or 0}, vector)
end
local zero = new(0,0)
local function fromPolar(angle, radius)
return new(cos(angle) * radius, sin(angle) * radius)
end
local function isvector(v)
return type(v) == 'table' and type(v.x) == 'number' and type(v.y) == 'number'
end
function vector:clone()
return new(self.x, self.y)
end
function vector:unpack()
return self.x, self.y
end
function vector:__tostring()
return "("..tonumber(self.x)..","..tonumber(self.y)..")"
end
function vector.__unm(a)
return new(-a.x, -a.y)
end
function vector.__add(a,b)
assert(isvector(a) and isvector(b), "Add: wrong argument types (<vector> expected)")
return new(a.x+b.x, a.y+b.y)
end
function vector.__sub(a,b)
assert(isvector(a) and isvector(b), "Sub: wrong argument types (<vector> expected)")
return new(a.x-b.x, a.y-b.y)
end
function vector.__mul(a,b)
if type(a) == "number" then
return new(a*b.x, a*b.y)
elseif type(b) == "number" then
return new(b*a.x, b*a.y)
else
assert(isvector(a) and isvector(b), "Mul: wrong argument types (<vector> or <number> expected)")
return a.x*b.x + a.y*b.y
end
end
function vector.__div(a,b)
assert(isvector(a) and type(b) == "number", "wrong argument types (expected <vector> / <number>)")
return new(a.x / b, a.y / b)
end
function vector.__eq(a,b)
return a.x == b.x and a.y == b.y
end
function vector.__lt(a,b)
return a.x < b.x or (a.x == b.x and a.y < b.y)
end
function vector.__le(a,b)
return a.x <= b.x and a.y <= b.y
end
function vector.permul(a,b)
assert(isvector(a) and isvector(b), "permul: wrong argument types (<vector> expected)")
return new(a.x*b.x, a.y*b.y)
end
function vector:toPolar()
return new(atan2(self.x, self.y), self:len())
end
function vector:len2()
return self.x * self.x + self.y * self.y
end
function vector:len()
return sqrt(self.x * self.x + self.y * self.y)
end
function vector.dist(a, b)
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
local dx = a.x - b.x
local dy = a.y - b.y
return sqrt(dx * dx + dy * dy)
end
function vector.dist2(a, b)
assert(isvector(a) and isvector(b), "dist: wrong argument types (<vector> expected)")
local dx = a.x - b.x
local dy = a.y - b.y
return (dx * dx + dy * dy)
end
function vector:normalizeInplace()
local l = self:len()
if l > 0 then
self.x, self.y = self.x / l, self.y / l
end
return self
end
function vector:normalized()
return self:clone():normalizeInplace()
end
function vector:rotateInplace(phi)
local c, s = cos(phi), sin(phi)
self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y
return self
end
function vector:rotated(phi)
local c, s = cos(phi), sin(phi)
return new(c * self.x - s * self.y, s * self.x + c * self.y)
end
function vector:perpendicular()
return new(-self.y, self.x)
end
function vector:projectOn(v)
assert(isvector(v), "invalid argument: cannot project vector on " .. type(v))
-- (self * v) * v / v:len2()
local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
return new(s * v.x, s * v.y)
end
function vector:mirrorOn(v)
assert(isvector(v), "invalid argument: cannot mirror vector on " .. type(v))
-- 2 * self:projectOn(v) - self
local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
return new(s * v.x - self.x, s * v.y - self.y)
end
function vector:cross(v)
assert(isvector(v), "cross: wrong argument types (<vector> expected)")
return self.x * v.y - self.y * v.x
end
-- ref.: http://blog.signalsondisplay.com/?p=336
function vector:trimInplace(maxLen)
local s = maxLen * maxLen / self:len2()
s = (s > 1 and 1) or math.sqrt(s)
self.x, self.y = self.x * s, self.y * s
return self
end
function vector:angleTo(other)
if other then
return atan2(self.y, self.x) - atan2(other.y, other.x)
end
return atan2(self.y, self.x)
end
function vector:trimmed(maxLen)
return self:clone():trimInplace(maxLen)
end
-- the module
return setmetatable({new = new, fromPolar = fromPolar, isvector = isvector, zero = zero},
{__call = function(_, ...) return new(...) end})

View file

@ -1,947 +0,0 @@
local bump = {
_VERSION = 'bump-3dpd v0.2.0',
_URL = 'https://github.com/oniietzschan/bump-3dpd',
_DESCRIPTION = 'A 3D collision detection library for Lua.',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota
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.
]]
}
------------------------------------------
-- Auxiliary functions
------------------------------------------
local DELTA = 1e-10 -- floating-point margin of error
local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
local function sign(x)
if x > 0 then return 1 end
if x == 0 then return 0 end
return -1
end
local function nearest(x, a, b)
if abs(a - x) < abs(b - x) then return a else return b end
end
local function assertType(desiredType, value, name)
if type(value) ~= desiredType then
error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
end
end
local function assertIsPositiveNumber(value, name)
if type(value) ~= 'number' or value <= 0 then
error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
end
end
local function assertIsCube(x,y,z,w,h,d)
assertType('number', x, 'x')
assertType('number', y, 'y')
assertType('number', z, 'z')
assertIsPositiveNumber(w, 'w')
assertIsPositiveNumber(h, 'h')
assertIsPositiveNumber(d, 'd')
end
local defaultFilter = function()
return 'slide'
end
------------------------------------------
-- Cube functions
------------------------------------------
local function cube_getNearestCorner(x,y,z,w,h,d, px, py, pz)
return nearest(px, x, x + w),
nearest(py, y, y + h),
nearest(pz, z, z + d)
end
-- This is a generalized implementation of the liang-barsky algorithm, which also returns
-- the normals of the sides where the segment intersects.
-- Returns nil if the segment never touches the cube
-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
local function cube_getSegmentIntersectionIndices(x,y,z,w,h,d, x1,y1,z1,x2,y2,z2, ti1,ti2)
ti1, ti2 = ti1 or 0, ti2 or 1
local dx = x2 - x1
local dy = y2 - y1
local dz = z2 - z1
local nx, ny, nz
local nx1, ny1, nz1, nx2, ny2, nz2 = 0,0,0,0,0,0
local p, q, r
for side = 1,6 do
if side == 1 then -- Left
nx,ny,nz,p,q = -1, 0, 0, -dx, x1 - x
elseif side == 2 then -- Right
nx,ny,nz,p,q = 1, 0, 0, dx, x + w - x1
elseif side == 3 then -- Top
nx,ny,nz,p,q = 0, -1, 0, -dy, y1 - y
elseif side == 4 then -- Bottom
nx,ny,nz,p,q = 0, 1, 0, dy, y + h - y1
elseif side == 5 then -- Front
nx,ny,nz,p,q = 0, 0, -1, -dz, z1 - z
else -- Back
nx,ny,nz,p,q = 0, 0, 1, dz, z + d - z1
end
if p == 0 then
if q <= 0 then
return nil
end
else
r = q / p
if p < 0 then
if r > ti2 then
return nil
elseif r > ti1 then
ti1, nx1,ny1,nz1 = r, nx,ny,nz
end
else -- p > 0
if r < ti1 then
return nil
elseif r < ti2 then
ti2, nx2,ny2,nz2 = r,nx,ny,nz
end
end
end
end
return ti1,ti2, nx1,ny1,nz1, nx2,ny2,nz2
end
-- Calculates the minkowsky difference between 2 cubes, which is another cube
local function cube_getDiff(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
return x2 - x1 - w1,
y2 - y1 - h1,
z2 - z1 - d1,
w1 + w2,
h1 + h2,
d1 + d2
end
local function cube_containsPoint(x,y,z,w,h,d, px,py,pz)
return px - x > DELTA
and py - y > DELTA
and pz - z > DELTA
and x + w - px > DELTA
and y + h - py > DELTA
and z + d - pz > DELTA
end
local function cube_isIntersecting(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
return x1 < x2 + w2 and x2 < x1 + w1 and
y1 < y2 + h2 and y2 < y1 + h1 and
z1 < z2 + d2 and z2 < z1 + d1
end
local function cube_getCubeDistance(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
local dx = x1 - x2 + (w1 - w2)/2
local dy = y1 - y2 + (h1 - h2)/2
local dz = z1 - z2 + (d1 - d2)/2
return (dx * dx) + (dy * dy) + (dz * dz)
end
local function cube_detectCollision(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2, goalX, goalY, goalZ)
goalX = goalX or x1
goalY = goalY or y1
goalZ = goalZ or z1
local dx = goalX - x1
local dy = goalY - y1
local dz = goalZ - z1
local x,y,z,w,h,d = cube_getDiff(x1,y1,z1,w1,h1,d1, x2,y2,z2,w2,h2,d2)
local overlaps, ti, nx, ny, nz
if cube_containsPoint(x,y,z,w,h,d, 0,0,0) then -- item was intersecting other
local px, py, pz = cube_getNearestCorner(x,y,z,w,h,d, 0,0,0)
-- Volume of intersection:
local wi = min(w1, abs(px))
local hi = min(h1, abs(py))
local di = min(d1, abs(pz))
ti = wi * hi * di * -1 -- ti is the negative volume of intersection
overlaps = true
else
local ti1,ti2,nx1,ny1,nz1 = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, 0,0,0,dx,dy,dz, -math.huge, math.huge)
-- item tunnels into other
if ti1
and ti1 < 1
and (abs(ti1 - ti2) >= DELTA) -- special case for cube going through another cube's corner
and (0 < ti1 + DELTA
or 0 == ti1 and ti2 > 0)
then
ti, nx, ny, nz = ti1, nx1, ny1, nz1
overlaps = false
end
end
if not ti then
return
end
local tx, ty, tz
if overlaps then
if dx == 0 and dy == 0 and dz == 0 then
-- intersecting and not moving - use minimum displacement vector
local px, py, pz = cube_getNearestCorner(x,y,z,w,h,d, 0,0,0)
if abs(px) <= abs(py) and abs(px) <= abs(pz) then
-- X axis has minimum displacement
py, pz = 0, 0
elseif abs(py) <= abs(pz) then
-- Y axis has minimum displacement
px, pz = 0, 0
else
-- Z axis has minimum displacement
px, py = 0, 0
end
nx, ny, nz = sign(px), sign(py), sign(pz)
tx = x1 + px
ty = y1 + py
tz = z1 + pz
else
-- intersecting and moving - move in the opposite direction
local ti1, _
ti1,_,nx,ny,nz = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, 0,0,0,dx,dy,dz, -math.huge, 1)
if not ti1 then
return
end
tx = x1 + dx * ti1
ty = y1 + dy * ti1
tz = z1 + dz * ti1
end
else -- tunnel
tx = x1 + dx * ti
ty = y1 + dy * ti
tz = z1 + dz * ti
end
return {
overlaps = overlaps,
ti = ti,
move = {x = dx, y = dy, z = dz},
normal = {x = nx, y = ny, z = nz},
touch = {x = tx, y = ty, z = tz},
itemCube = {x = x1, y = y1, z = z1, w = w1, h = h1, d = d1},
otherCube = {x = x2, y = y2, z = z2, w = w2, h = h2, d = d2},
}
end
------------------------------------------
-- Grid functions
------------------------------------------
local function grid_toWorld(cellSize, cx, cy, cz)
return (cx - 1) * cellSize,
(cy - 1) * cellSize,
(cz - 1) * cellSize
end
local function grid_toCell(cellSize, x, y, z)
return floor(x / cellSize) + 1,
floor(y / cellSize) + 1,
floor(z / cellSize) + 1
end
-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
-- It has been modified to include both cells when the ray "touches a grid corner",
-- and with a different exit condition
local function grid_traverse_initStep(cellSize, ct, t1, t2)
local v = t2 - t1
if v > 0 then
return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
elseif v < 0 then
return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
else
return 0, math.huge, math.huge
end
end
local function grid_traverse(cellSize, x1,y1,z1,x2,y2,z2, f)
local cx1, cy1, cz1 = grid_toCell(cellSize, x1, y1, z1)
local cx2, cy2, cz2 = grid_toCell(cellSize, x2, y2, z2)
local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
local stepZ, dz, tz = grid_traverse_initStep(cellSize, cz1, z1, z2)
local cx, cy, cz = cx1, cy1, cz1
f(cx, cy, cz)
-- The default implementation had an infinite loop problem when
-- approaching the last cell in some occassions. We finish iterating
-- when we are *next* to the last cell
while abs(cx - cx2) + abs(cy - cy2) + abs(cz - cz2) > 1 do
if tx < ty and tx < tz then -- tx is smallest
tx = tx + dx
cx = cx + stepX
f(cx, cy, cz)
elseif ty < tz then -- ty is smallest
-- Addition: include both cells when going through corners
if tx == ty then
f(cx + stepX, cy, cz)
end
ty = ty + dy
cy = cy + stepY
f(cx, cy, cz)
else -- tz is smallest
-- Addition: include both cells when going through corners
if tx == tz then
f(cx + stepX, cy, cz)
end
if ty == tz then
f(cx, cy + stepY, cz)
end
tz = tz + dz
cz = cz + stepZ
f(cx, cy, cz)
end
end
-- If we have not arrived to the last cell, use it
if cx ~= cx2 or cy ~= cy2 or cz ~= cz2 then
f(cx2, cy2, cz2)
end
end
local function grid_toCellCube(cellSize, x,y,z,w,h,d)
local cx,cy,cz = grid_toCell(cellSize, x, y, z)
local cx2 = ceil((x + w) / cellSize)
local cy2 = ceil((y + h) / cellSize)
local cz2 = ceil((z + d) / cellSize)
return cx,
cy,
cz,
cx2 - cx + 1,
cy2 - cy + 1,
cz2 - cz + 1
end
------------------------------------------
-- Responses
------------------------------------------
local touch = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
return col.touch.x, col.touch.y, col.touch.z, {}, 0
end
local cross = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
local cols, len = world:project(col.item, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
return goalX, goalY, goalZ, cols, len
end
local slide = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
goalX = goalX or x
goalY = goalY or y
goalZ = goalZ or z
local tch, move = col.touch, col.move
if move.x ~= 0 or move.y ~= 0 or move.z ~= 0 then
if col.normal.x ~= 0 then
goalX = tch.x
end
if col.normal.y ~= 0 then
goalY = tch.y
end
if col.normal.z ~= 0 then
goalZ = tch.z
end
end
col.slide = {x = goalX, y = goalY, z = goalZ}
x, y, z = tch.x, tch.y, tch.z
local cols, len = world:project(col.item, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
return goalX, goalY, goalZ, cols, len
end
local bounce = function(world, col, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
goalX = goalX or x
goalY = goalY or y
goalZ = goalZ or z
local tch, move = col.touch, col.move
local tx, ty, tz = tch.x, tch.y, tch.z
local bx, by, bz = tx, ty, tz
if move.x ~= 0 or move.y ~= 0 or move.z ~= 0 then
local bnx = goalX - tx
local bny = goalY - ty
local bnz = goalZ - tz
if col.normal.x ~= 0 then
bnx = -bnx
end
if col.normal.y ~= 0 then
bny = -bny
end
if col.normal.z ~= 0 then
bnz = -bnz
end
bx = tx + bnx
by = ty + bny
bz = tz + bnz
end
col.bounce = {x = bx, y = by, z = bz}
x, y, z = tch.x, tch.y, tch.z
goalX, goalY, goalZ = bx, by, bz
local cols, len = world:project(col.item, x,y,z,w,h,d, goalX, goalY, goalZ, filter)
return goalX, goalY, goalZ, cols, len
end
------------------------------------------
-- World
------------------------------------------
local World = {}
local World_mt = {__index = World}
-- Private functions and methods
local function sortByWeight(a,b)
return a.weight < b.weight
end
local function sortByTiAndDistance(a,b)
if a.ti == b.ti then
local ir, ar, br = a.itemCube, a.otherCube, b.otherCube
local ad = cube_getCubeDistance(ir.x,ir.y,ir.z,ir.w,ir.h,ir.d, ar.x,ar.y,ar.z,ar.w,ar.h,ar.d)
local bd = cube_getCubeDistance(ir.x,ir.y,ir.z,ir.w,ir.h,ir.d, br.x,br.y,br.z,br.w,br.h,br.d)
return ad < bd
end
return a.ti < b.ti
end
local function addItemToCell(self, item, cx, cy, cz)
self.cells[cz] = self.cells[cz] or {}
self.cells[cz][cy] = self.cells[cz][cy] or setmetatable({}, {__mode = 'v'})
if self.cells[cz][cy][cx] == nil then
self.cells[cz][cy][cx] = {
itemCount = 0,
x = cx,
y = cy,
z = cz,
items = setmetatable({}, {__mode = 'k'})
}
end
local cell = self.cells[cz][cy][cx]
self.nonEmptyCells[cell] = true
if not cell.items[item] then
cell.items[item] = true
cell.itemCount = cell.itemCount + 1
end
end
local function removeItemFromCell(self, item, cx, cy, cz)
if not self.cells[cz]
or not self.cells[cz][cy]
or not self.cells[cz][cy][cx]
or not self.cells[cz][cy][cx].items[item]
then
return false
end
local cell = self.cells[cz][cy][cx]
cell.items[item] = nil
cell.itemCount = cell.itemCount - 1
if cell.itemCount == 0 then
self.nonEmptyCells[cell] = nil
end
return true
end
local function getDictItemsInCellCube(self, cx,cy,cz, cw,ch,cd)
local items_dict = {}
for z = cz, cz + cd - 1 do
local plane = self.cells[z]
if plane then
for y = cy, cy + ch - 1 do
local row = plane[y]
if row then
for x = cx, cx + cw - 1 do
local cell = row[x]
if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
for item,_ in pairs(cell.items) do
items_dict[item] = true
end
end
end
end
end
end
end
return items_dict
end
local function getCellsTouchedBySegment(self, x1,y1,z1,x2,y2,z2)
local cells, cellsLen, visited = {}, 0, {}
grid_traverse(self.cellSize, x1,y1,z1,x2,y2,z2, function(cx, cy, cz)
local plane = self.cells[cz]
if not plane then
return
end
local row = plane[cy]
if not row then
return
end
local cell = row[cx]
if not cell or visited[cell] then
return
end
visited[cell] = true
cellsLen = cellsLen + 1
cells[cellsLen] = cell
end)
return cells, cellsLen
end
local function getInfoAboutItemsTouchedBySegment(self, x1,y1,z1, x2,y2,z2, filter)
local cells, len = getCellsTouchedBySegment(self, x1,y1,z1,x2,y2,z2)
local cell, cube, x,y,z,w,h,d, ti1, ti2, tii0,tii1
local visited, itemInfo, itemInfoLen = {}, {}, 0
for i = 1, len do
cell = cells[i]
for item in pairs(cell.items) do
if not visited[item] then
visited[item] = true
if (not filter or filter(item)) then
cube = self.cubes[item]
x, y, z, w, h, d = cube.x, cube.y, cube.z, cube.w, cube.h, cube.d
ti1, ti2 = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, x1,y1,z1, x2,y2,z2, 0, 1)
if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
-- the sorting is according to the t of an infinite line, not the segment
tii0, tii1 = cube_getSegmentIntersectionIndices(x,y,z,w,h,d, x1,y1,z1, x2,y2,z2, -math.huge, math.huge)
itemInfoLen = itemInfoLen + 1
itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0, tii1)}
end
end
end
end
end
table.sort(itemInfo, sortByWeight)
return itemInfo, itemInfoLen
end
local function getResponseByName(self, name)
local response = self.responses[name]
if not response then
error(('Unknown collision type: %s (%s)'):format(name, type(name)))
end
return response
end
-- Misc Public Methods
function World:addResponse(name, response)
self.responses[name] = response
end
function World:projectMove(item, x,y,z,w,h,d, goalX,goalY,goalZ, filter)
local cols, len = {}, 0
filter = filter or defaultFilter
local visited = {[item] = true}
local visitedFilter = function(itm, other)
if visited[other] then
return false
end
return filter(itm, other)
end
local projected_cols, projected_len = self:project(item, x,y,z,w,h,d, goalX,goalY,goalZ, visitedFilter)
while projected_len > 0 do
local col = projected_cols[1]
len = len + 1
cols[len] = col
visited[col.other] = true
local response = getResponseByName(self, col.type)
goalX, goalY, goalZ, projected_cols, projected_len = response(
self,
col,
x, y, z, w, h, d,
goalX, goalY, goalZ,
visitedFilter
)
end
return goalX, goalY, goalZ, cols, len
end
function World:project(item, x,y,z,w,h,d, goalX,goalY,goalZ, filter)
assertIsCube(x, y, z, w, h, d)
goalX = goalX or x
goalY = goalY or y
goalZ = goalZ or z
filter = filter or defaultFilter
local collisions, len = {}, 0
local visited = {}
if item ~= nil then
visited[item] = true
end
-- This could probably be done with less cells using a polygon raster over the cells instead of a
-- bounding cube of the whole movement. Conditional to building a queryPolygon method
local tx = min(goalX, x)
local ty = min(goalY, y)
local tz = min(goalZ, z)
local tx2 = max(goalX + w, x + w)
local ty2 = max(goalY + h, y + h)
local tz2 = max(goalZ + d, z + d)
local tw = tx2 - tx
local th = ty2 - ty
local td = tz2 - tz
local cx,cy,cz,cw,ch,cd = grid_toCellCube(self.cellSize, tx,ty,tz, tw,th,td)
local dictItemsInCellCube = getDictItemsInCellCube(self, cx,cy,cz,cw,ch,cd)
for other,_ in pairs(dictItemsInCellCube) do
if not visited[other] then
visited[other] = true
local responseName = filter(item, other)
if responseName then
local ox,oy,oz,ow,oh,od = self:getCube(other)
local col = cube_detectCollision(x,y,z,w,h,d, ox,oy,oz,ow,oh,od, goalX, goalY, goalZ)
if col then
col.other = other
col.item = item
col.type = responseName
len = len + 1
collisions[len] = col
end
end
end
end
table.sort(collisions, sortByTiAndDistance)
return collisions, len
end
function World:countCells()
local count = 0
for _, plane in pairs(self.cells) do
for _, row in pairs(plane) do
for _,_ in pairs(row) do
count = count + 1
end
end
end
return count
end
function World:hasItem(item)
return not not self.cubes[item]
end
function World:getItems()
local items, len = {}, 0
for item,_ in pairs(self.cubes) do
len = len + 1
items[len] = item
end
return items, len
end
function World:countItems()
local len = 0
for _ in pairs(self.cubes) do len = len + 1 end
return len
end
function World:getCube(item)
local cube = self.cubes[item]
if not cube then
error('Item ' .. tostring(item) .. ' must be added to the world before getting its cube. Use world:add(item, x,y,z,w,h,d) to add it first.')
end
return cube.x, cube.y, cube.z, cube.w, cube.h, cube.d
end
function World:toWorld(cx, cy, cz)
return grid_toWorld(self.cellSize, cx, cy, cz)
end
function World:toCell(x,y,z)
return grid_toCell(self.cellSize, x, y, z)
end
-- Query methods
function World:queryCube(x,y,z,w,h,d, filter)
assertIsCube(x,y,z,w,h,d)
local cx,cy,cz,cw,ch,cd = grid_toCellCube(self.cellSize, x,y,z,w,h,d)
local dictItemsInCellCube = getDictItemsInCellCube(self, cx,cy,cz,cw,ch,cd)
local items, len = {}, 0
local cube
for item,_ in pairs(dictItemsInCellCube) do
cube = self.cubes[item]
if (not filter or filter(item))
and cube_isIntersecting(x,y,z,w,h,d, cube.x, cube.y, cube.z, cube.w, cube.h, cube.d)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:queryPoint(x,y,z, filter)
local cx,cy,cz = self:toCell(x,y,z)
local dictItemsInCellCube = getDictItemsInCellCube(self, cx,cy,cz, 1,1,1)
local items, len = {}, 0
local cube
for item,_ in pairs(dictItemsInCellCube) do
cube = self.cubes[item]
if (not filter or filter(item))
and cube_containsPoint(cube.x, cube.y, cube.z, cube.w, cube.h, cube.d, x, y, z)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:querySegment(x1, y1, z1, x2, y2, z2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, z1, x2, y2, z2, filter)
local items = {}
for i = 1, len do
items[i] = itemInfo[i].item
end
return items, len
end
-- function World:querySegmentWithCoords(x1, y1, z1, x2, y2, z2, filter)
-- local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, z1, x2, y2, z2, filter)
-- local dx, dy, dz = x2 - x1, y2 - y1, z2 - z1
-- local info, ti1, ti2
-- for i=1, len do
-- info = itemInfo[i]
-- ti1 = info.ti1
-- ti2 = info.ti2
-- info.weight = nil
-- info.x1 = x1 + dx * ti1
-- info.y1 = y1 + dy * ti1
-- info.x2 = x1 + dx * ti2
-- info.y2 = y1 + dy * ti2
-- end
-- return itemInfo, len
-- end
--- Main methods
function World:add(item, x,y,z,w,h,d)
local cube = self.cubes[item]
if cube then
error('Item ' .. tostring(item) .. ' added to the world twice.')
end
assertIsCube(x,y,z,w,h,d)
self.cubes[item] = {x=x,y=y,z=z,w=w,h=h,d=d}
local cl,ct,cs,cw,ch,cd = grid_toCellCube(self.cellSize, x,y,z,w,h,d)
for cz = cs, cs + cd - 1 do
for cy = ct, ct + ch - 1 do
for cx = cl, cl + cw - 1 do
addItemToCell(self, item, cx, cy, cz)
end
end
end
return item
end
function World:remove(item)
local x,y,z,w,h,d = self:getCube(item)
self.cubes[item] = nil
local cl,ct,cs,cw,ch,cd = grid_toCellCube(self.cellSize, x,y,z,w,h,d)
for cz = cs, cs + cd - 1 do
for cy = ct, ct + ch - 1 do
for cx = cl, cl + cw - 1 do
removeItemFromCell(self, item, cx, cy, cz)
end
end
end
end
function World:update(item, x2,y2,z2,w2,h2,d2)
local x1,y1,z1, w1,h1,d1 = self:getCube(item)
w2 = w2 or w1
h2 = h2 or h1
d2 = d2 or d1
assertIsCube(x2,y2,z2,w2,h2,d2)
if x1 == x2 and y1 == y2 and z1 == z2 and w1 == w2 and h1 == h2 and d1 == d2 then
return
end
local cl1,ct1,cs1,cw1,ch1,cd1 = grid_toCellCube(self.cellSize, x1,y1,z1, w1,h1,d1)
local cl2,ct2,cs2,cw2,ch2,cd2 = grid_toCellCube(self.cellSize, x2,y2,z2, w2,h2,d2)
if cl1 ~= cl2 or ct1 ~= ct2 or cs1 ~= cs2 or cw1 ~= cw2 or ch1 ~= ch2 or cd1 ~= cd2 then
local cr1 = cl1 + cw1 - 1
local cr2 = cl2 + cw2 - 1
local cb1 = ct1 + ch1 - 1
local cb2 = ct2 + ch2 - 1
local css1 = cs1 + cd1 - 1
local css2 = cs2 + cd2 - 1
local cyOut, czOut
for cz = cs1, css1 do
czOut = cz < cs2 or cz > css2
for cy = ct1, cb1 do
cyOut = cy < ct2 or cy > cb2
for cx = cl1, cr1 do
if czOut or cyOut or cx < cl2 or cx > cr2 then
removeItemFromCell(self, item, cx, cy, cz)
end
end
end
end
for cz = cs2, css2 do
czOut = cz < cs1 or cz > css1
for cy = ct2, cb2 do
cyOut = cy < ct1 or cy > cb1
for cx = cl2, cr2 do
if czOut or cyOut or cx < cl1 or cx > cr1 then
addItemToCell(self, item, cx, cy, cz)
end
end
end
end
end
local cube = self.cubes[item]
cube.x, cube.y, cube.z, cube.w, cube.h, cube.d = x2, y2, z2, w2, h2, d2
end
function World:move(item, goalX, goalY, goalZ, filter)
local actualX, actualY, actualZ, cols, len = self:check(item, goalX, goalY, goalZ, filter)
self:update(item, actualX, actualY, actualZ)
return actualX, actualY, actualZ, cols, len
end
function World:check(item, goalX, goalY, goalZ, filter)
local x,y,z,w,h,d = self:getCube(item)
return self:projectMove(item, x,y,z,w,h,d, goalX,goalY,goalZ, filter)
end
-- Public library functions
bump.newWorld = function(cellSize)
cellSize = cellSize or 64
assertIsPositiveNumber(cellSize, 'cellSize')
local world = setmetatable({
cellSize = cellSize,
cubes = {},
cells = {},
nonEmptyCells = {},
responses = {},
}, World_mt)
world:addResponse('touch', touch)
world:addResponse('cross', cross)
world:addResponse('slide', slide)
world:addResponse('bounce', bounce)
return world
end
bump.cube = {
getNearestCorner = cube_getNearestCorner,
getSegmentIntersectionIndices = cube_getSegmentIntersectionIndices,
getDiff = cube_getDiff,
containsPoint = cube_containsPoint,
isIntersecting = cube_isIntersecting,
getCubeDistance = cube_getCubeDistance,
detectCollision = cube_detectCollision
}
bump.responses = {
touch = touch,
cross = cross,
slide = slide,
bounce = bounce
}
return bump

View file

@ -1,769 +0,0 @@
local bump = {
_VERSION = 'bump v3.1.7',
_URL = 'https://github.com/kikito/bump.lua',
_DESCRIPTION = 'A collision detection library for Lua',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2014 Enrique García Cota
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.
]]
}
------------------------------------------
-- Auxiliary functions
------------------------------------------
local DELTA = 1e-10 -- floating-point margin of error
local abs, floor, ceil, min, max = math.abs, math.floor, math.ceil, math.min, math.max
local function sign(x)
if x > 0 then return 1 end
if x == 0 then return 0 end
return -1
end
local function nearest(x, a, b)
if abs(a - x) < abs(b - x) then return a else return b end
end
local function assertType(desiredType, value, name)
if type(value) ~= desiredType then
error(name .. ' must be a ' .. desiredType .. ', but was ' .. tostring(value) .. '(a ' .. type(value) .. ')')
end
end
local function assertIsPositiveNumber(value, name)
if type(value) ~= 'number' or value <= 0 then
error(name .. ' must be a positive integer, but was ' .. tostring(value) .. '(' .. type(value) .. ')')
end
end
local function assertIsRect(x,y,w,h)
assertType('number', x, 'x')
assertType('number', y, 'y')
assertIsPositiveNumber(w, 'w')
assertIsPositiveNumber(h, 'h')
end
local defaultFilter = function()
return 'slide'
end
------------------------------------------
-- Rectangle functions
------------------------------------------
local function rect_getNearestCorner(x,y,w,h, px, py)
return nearest(px, x, x+w), nearest(py, y, y+h)
end
-- This is a generalized implementation of the liang-barsky algorithm, which also returns
-- the normals of the sides where the segment intersects.
-- Returns nil if the segment never touches the rect
-- Notice that normals are only guaranteed to be accurate when initially ti1, ti2 == -math.huge, math.huge
local function rect_getSegmentIntersectionIndices(x,y,w,h, x1,y1,x2,y2, ti1,ti2)
ti1, ti2 = ti1 or 0, ti2 or 1
local dx, dy = x2-x1, y2-y1
local nx, ny
local nx1, ny1, nx2, ny2 = 0,0,0,0
local p, q, r
for side = 1,4 do
if side == 1 then nx,ny,p,q = -1, 0, -dx, x1 - x -- left
elseif side == 2 then nx,ny,p,q = 1, 0, dx, x + w - x1 -- right
elseif side == 3 then nx,ny,p,q = 0, -1, -dy, y1 - y -- top
else nx,ny,p,q = 0, 1, dy, y + h - y1 -- bottom
end
if p == 0 then
if q <= 0 then return nil end
else
r = q / p
if p < 0 then
if r > ti2 then return nil
elseif r > ti1 then ti1,nx1,ny1 = r,nx,ny
end
else -- p > 0
if r < ti1 then return nil
elseif r < ti2 then ti2,nx2,ny2 = r,nx,ny
end
end
end
end
return ti1,ti2, nx1,ny1, nx2,ny2
end
-- Calculates the minkowsky difference between 2 rects, which is another rect
local function rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
return x2 - x1 - w1,
y2 - y1 - h1,
w1 + w2,
h1 + h2
end
local function rect_containsPoint(x,y,w,h, px,py)
return px - x > DELTA and py - y > DELTA and
x + w - px > DELTA and y + h - py > DELTA
end
local function rect_isIntersecting(x1,y1,w1,h1, x2,y2,w2,h2)
return x1 < x2+w2 and x2 < x1+w1 and
y1 < y2+h2 and y2 < y1+h1
end
local function rect_getSquareDistance(x1,y1,w1,h1, x2,y2,w2,h2)
local dx = x1 - x2 + (w1 - w2)/2
local dy = y1 - y2 + (h1 - h2)/2
return dx*dx + dy*dy
end
local function rect_detectCollision(x1,y1,w1,h1, x2,y2,w2,h2, goalX, goalY)
goalX = goalX or x1
goalY = goalY or y1
local dx, dy = goalX - x1, goalY - y1
local x,y,w,h = rect_getDiff(x1,y1,w1,h1, x2,y2,w2,h2)
local overlaps, ti, nx, ny
if rect_containsPoint(x,y,w,h, 0,0) then -- item was intersecting other
local px, py = rect_getNearestCorner(x,y,w,h, 0, 0)
local wi, hi = min(w1, abs(px)), min(h1, abs(py)) -- area of intersection
ti = -wi * hi -- ti is the negative area of intersection
overlaps = true
else
local ti1,ti2,nx1,ny1 = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, math.huge)
-- item tunnels into other
if ti1
and ti1 < 1
and (abs(ti1 - ti2) >= DELTA) -- special case for rect going through another rect's corner
and (0 < ti1 + DELTA
or 0 == ti1 and ti2 > 0)
then
ti, nx, ny = ti1, nx1, ny1
overlaps = false
end
end
if not ti then return end
local tx, ty
if overlaps then
if dx == 0 and dy == 0 then
-- intersecting and not moving - use minimum displacement vector
local px, py = rect_getNearestCorner(x,y,w,h, 0,0)
if abs(px) < abs(py) then py = 0 else px = 0 end
nx, ny = sign(px), sign(py)
tx, ty = x1 + px, y1 + py
else
-- intersecting and moving - move in the opposite direction
local ti1, _
ti1,_,nx,ny = rect_getSegmentIntersectionIndices(x,y,w,h, 0,0,dx,dy, -math.huge, 1)
if not ti1 then return end
tx, ty = x1 + dx * ti1, y1 + dy * ti1
end
else -- tunnel
tx, ty = x1 + dx * ti, y1 + dy * ti
end
return {
overlaps = overlaps,
ti = ti,
move = {x = dx, y = dy},
normal = {x = nx, y = ny},
touch = {x = tx, y = ty},
itemRect = {x = x1, y = y1, w = w1, h = h1},
otherRect = {x = x2, y = y2, w = w2, h = h2}
}
end
------------------------------------------
-- Grid functions
------------------------------------------
local function grid_toWorld(cellSize, cx, cy)
return (cx - 1)*cellSize, (cy-1)*cellSize
end
local function grid_toCell(cellSize, x, y)
return floor(x / cellSize) + 1, floor(y / cellSize) + 1
end
-- grid_traverse* functions are based on "A Fast Voxel Traversal Algorithm for Ray Tracing",
-- by John Amanides and Andrew Woo - http://www.cse.yorku.ca/~amana/research/grid.pdf
-- It has been modified to include both cells when the ray "touches a grid corner",
-- and with a different exit condition
local function grid_traverse_initStep(cellSize, ct, t1, t2)
local v = t2 - t1
if v > 0 then
return 1, cellSize / v, ((ct + v) * cellSize - t1) / v
elseif v < 0 then
return -1, -cellSize / v, ((ct + v - 1) * cellSize - t1) / v
else
return 0, math.huge, math.huge
end
end
local function grid_traverse(cellSize, x1,y1,x2,y2, f)
local cx1,cy1 = grid_toCell(cellSize, x1,y1)
local cx2,cy2 = grid_toCell(cellSize, x2,y2)
local stepX, dx, tx = grid_traverse_initStep(cellSize, cx1, x1, x2)
local stepY, dy, ty = grid_traverse_initStep(cellSize, cy1, y1, y2)
local cx,cy = cx1,cy1
f(cx, cy)
-- The default implementation had an infinite loop problem when
-- approaching the last cell in some occassions. We finish iterating
-- when we are *next* to the last cell
while abs(cx - cx2) + abs(cy - cy2) > 1 do
if tx < ty then
tx, cx = tx + dx, cx + stepX
f(cx, cy)
else
-- Addition: include both cells when going through corners
if tx == ty then f(cx + stepX, cy) end
ty, cy = ty + dy, cy + stepY
f(cx, cy)
end
end
-- If we have not arrived to the last cell, use it
if cx ~= cx2 or cy ~= cy2 then f(cx2, cy2) end
end
local function grid_toCellRect(cellSize, x,y,w,h)
local cx,cy = grid_toCell(cellSize, x, y)
local cr,cb = ceil((x+w) / cellSize), ceil((y+h) / cellSize)
return cx, cy, cr - cx + 1, cb - cy + 1
end
------------------------------------------
-- Responses
------------------------------------------
local touch = function(world, col, x,y,w,h, goalX, goalY, filter)
return col.touch.x, col.touch.y, {}, 0
end
local cross = function(world, col, x,y,w,h, goalX, goalY, filter)
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
local slide = function(world, col, x,y,w,h, goalX, goalY, filter)
goalX = goalX or x
goalY = goalY or y
local tch, move = col.touch, col.move
if move.x ~= 0 or move.y ~= 0 then
if col.normal.x ~= 0 then
goalX = tch.x
else
goalY = tch.y
end
end
col.slide = {x = goalX, y = goalY}
x,y = tch.x, tch.y
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
local bounce = function(world, col, x,y,w,h, goalX, goalY, filter)
goalX = goalX or x
goalY = goalY or y
local tch, move = col.touch, col.move
local tx, ty = tch.x, tch.y
local bx, by = tx, ty
if move.x ~= 0 or move.y ~= 0 then
local bnx, bny = goalX - tx, goalY - ty
if col.normal.x == 0 then bny = -bny else bnx = -bnx end
bx, by = tx + bnx, ty + bny
end
col.bounce = {x = bx, y = by}
x,y = tch.x, tch.y
goalX, goalY = bx, by
local cols, len = world:project(col.item, x,y,w,h, goalX, goalY, filter)
return goalX, goalY, cols, len
end
------------------------------------------
-- World
------------------------------------------
local World = {}
local World_mt = {__index = World}
-- Private functions and methods
local function sortByWeight(a,b) return a.weight < b.weight end
local function sortByTiAndDistance(a,b)
if a.ti == b.ti then
local ir, ar, br = a.itemRect, a.otherRect, b.otherRect
local ad = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, ar.x,ar.y,ar.w,ar.h)
local bd = rect_getSquareDistance(ir.x,ir.y,ir.w,ir.h, br.x,br.y,br.w,br.h)
return ad < bd
end
return a.ti < b.ti
end
local function addItemToCell(self, item, cx, cy)
self.rows[cy] = self.rows[cy] or setmetatable({}, {__mode = 'v'})
local row = self.rows[cy]
row[cx] = row[cx] or {itemCount = 0, x = cx, y = cy, items = setmetatable({}, {__mode = 'k'})}
local cell = row[cx]
self.nonEmptyCells[cell] = true
if not cell.items[item] then
cell.items[item] = true
cell.itemCount = cell.itemCount + 1
end
end
local function removeItemFromCell(self, item, cx, cy)
local row = self.rows[cy]
if not row or not row[cx] or not row[cx].items[item] then return false end
local cell = row[cx]
cell.items[item] = nil
cell.itemCount = cell.itemCount - 1
if cell.itemCount == 0 then
self.nonEmptyCells[cell] = nil
end
return true
end
local function getDictItemsInCellRect(self, cl,ct,cw,ch)
local items_dict = {}
for cy=ct,ct+ch-1 do
local row = self.rows[cy]
if row then
for cx=cl,cl+cw-1 do
local cell = row[cx]
if cell and cell.itemCount > 0 then -- no cell.itemCount > 1 because tunneling
for item,_ in pairs(cell.items) do
items_dict[item] = true
end
end
end
end
end
return items_dict
end
local function getCellsTouchedBySegment(self, x1,y1,x2,y2)
local cells, cellsLen, visited = {}, 0, {}
grid_traverse(self.cellSize, x1,y1,x2,y2, function(cx, cy)
local row = self.rows[cy]
if not row then return end
local cell = row[cx]
if not cell or visited[cell] then return end
visited[cell] = true
cellsLen = cellsLen + 1
cells[cellsLen] = cell
end)
return cells, cellsLen
end
local function getInfoAboutItemsTouchedBySegment(self, x1,y1, x2,y2, filter)
local cells, len = getCellsTouchedBySegment(self, x1,y1,x2,y2)
local cell, rect, l,t,w,h, ti1,ti2, tii0,tii1
local visited, itemInfo, itemInfoLen = {},{},0
for i=1,len do
cell = cells[i]
for item in pairs(cell.items) do
if not visited[item] then
visited[item] = true
if (not filter or filter(item)) then
rect = self.rects[item]
l,t,w,h = rect.x,rect.y,rect.w,rect.h
ti1,ti2 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, 0, 1)
if ti1 and ((0 < ti1 and ti1 < 1) or (0 < ti2 and ti2 < 1)) then
-- the sorting is according to the t of an infinite line, not the segment
tii0,tii1 = rect_getSegmentIntersectionIndices(l,t,w,h, x1,y1, x2,y2, -math.huge, math.huge)
itemInfoLen = itemInfoLen + 1
itemInfo[itemInfoLen] = {item = item, ti1 = ti1, ti2 = ti2, weight = min(tii0,tii1)}
end
end
end
end
end
table.sort(itemInfo, sortByWeight)
return itemInfo, itemInfoLen
end
local function getResponseByName(self, name)
local response = self.responses[name]
if not response then
error(('Unknown collision type: %s (%s)'):format(name, type(name)))
end
return response
end
-- Misc Public Methods
function World:addResponse(name, response)
self.responses[name] = response
end
function World:project(item, x,y,w,h, goalX, goalY, filter)
assertIsRect(x,y,w,h)
goalX = goalX or x
goalY = goalY or y
filter = filter or defaultFilter
local collisions, len = {}, 0
local visited = {}
if item ~= nil then visited[item] = true end
-- This could probably be done with less cells using a polygon raster over the cells instead of a
-- bounding rect of the whole movement. Conditional to building a queryPolygon method
local tl, tt = min(goalX, x), min(goalY, y)
local tr, tb = max(goalX + w, x+w), max(goalY + h, y+h)
local tw, th = tr-tl, tb-tt
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, tl,tt,tw,th)
local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
for other,_ in pairs(dictItemsInCellRect) do
if not visited[other] then
visited[other] = true
local responseName = filter(item, other)
if responseName then
local ox,oy,ow,oh = self:getRect(other)
local col = rect_detectCollision(x,y,w,h, ox,oy,ow,oh, goalX, goalY)
if col then
col.other = other
col.item = item
col.type = responseName
len = len + 1
collisions[len] = col
end
end
end
end
table.sort(collisions, sortByTiAndDistance)
return collisions, len
end
function World:countCells()
local count = 0
for _,row in pairs(self.rows) do
for _,_ in pairs(row) do
count = count + 1
end
end
return count
end
function World:hasItem(item)
return not not self.rects[item]
end
function World:getItems()
local items, len = {}, 0
for item,_ in pairs(self.rects) do
len = len + 1
items[len] = item
end
return items, len
end
function World:countItems()
local len = 0
for _ in pairs(self.rects) do len = len + 1 end
return len
end
function World:getRect(item)
local rect = self.rects[item]
if not rect then
error('Item ' .. tostring(item) .. ' must be added to the world before getting its rect. Use world:add(item, x,y,w,h) to add it first.')
end
return rect.x, rect.y, rect.w, rect.h
end
function World:toWorld(cx, cy)
return grid_toWorld(self.cellSize, cx, cy)
end
function World:toCell(x,y)
return grid_toCell(self.cellSize, x, y)
end
--- Query methods
function World:queryRect(x,y,w,h, filter)
assertIsRect(x,y,w,h)
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
local dictItemsInCellRect = getDictItemsInCellRect(self, cl,ct,cw,ch)
local items, len = {}, 0
local rect
for item,_ in pairs(dictItemsInCellRect) do
rect = self.rects[item]
if (not filter or filter(item))
and rect_isIntersecting(x,y,w,h, rect.x, rect.y, rect.w, rect.h)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:queryPoint(x,y, filter)
local cx,cy = self:toCell(x,y)
local dictItemsInCellRect = getDictItemsInCellRect(self, cx,cy,1,1)
local items, len = {}, 0
local rect
for item,_ in pairs(dictItemsInCellRect) do
rect = self.rects[item]
if (not filter or filter(item))
and rect_containsPoint(rect.x, rect.y, rect.w, rect.h, x, y)
then
len = len + 1
items[len] = item
end
end
return items, len
end
function World:querySegment(x1, y1, x2, y2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
local items = {}
for i=1, len do
items[i] = itemInfo[i].item
end
return items, len
end
function World:querySegmentWithCoords(x1, y1, x2, y2, filter)
local itemInfo, len = getInfoAboutItemsTouchedBySegment(self, x1, y1, x2, y2, filter)
local dx, dy = x2-x1, y2-y1
local info, ti1, ti2
for i=1, len do
info = itemInfo[i]
ti1 = info.ti1
ti2 = info.ti2
info.weight = nil
info.x1 = x1 + dx * ti1
info.y1 = y1 + dy * ti1
info.x2 = x1 + dx * ti2
info.y2 = y1 + dy * ti2
end
return itemInfo, len
end
--- Main methods
function World:add(item, x,y,w,h)
local rect = self.rects[item]
if rect then
error('Item ' .. tostring(item) .. ' added to the world twice.')
end
assertIsRect(x,y,w,h)
self.rects[item] = {x=x,y=y,w=w,h=h}
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
for cy = ct, ct+ch-1 do
for cx = cl, cl+cw-1 do
addItemToCell(self, item, cx, cy)
end
end
return item
end
function World:remove(item)
local x,y,w,h = self:getRect(item)
self.rects[item] = nil
local cl,ct,cw,ch = grid_toCellRect(self.cellSize, x,y,w,h)
for cy = ct, ct+ch-1 do
for cx = cl, cl+cw-1 do
removeItemFromCell(self, item, cx, cy)
end
end
end
function World:update(item, x2,y2,w2,h2)
local x1,y1,w1,h1 = self:getRect(item)
w2,h2 = w2 or w1, h2 or h1
assertIsRect(x2,y2,w2,h2)
if x1 ~= x2 or y1 ~= y2 or w1 ~= w2 or h1 ~= h2 then
local cellSize = self.cellSize
local cl1,ct1,cw1,ch1 = grid_toCellRect(cellSize, x1,y1,w1,h1)
local cl2,ct2,cw2,ch2 = grid_toCellRect(cellSize, x2,y2,w2,h2)
if cl1 ~= cl2 or ct1 ~= ct2 or cw1 ~= cw2 or ch1 ~= ch2 then
local cr1, cb1 = cl1+cw1-1, ct1+ch1-1
local cr2, cb2 = cl2+cw2-1, ct2+ch2-1
local cyOut
for cy = ct1, cb1 do
cyOut = cy < ct2 or cy > cb2
for cx = cl1, cr1 do
if cyOut or cx < cl2 or cx > cr2 then
removeItemFromCell(self, item, cx, cy)
end
end
end
for cy = ct2, cb2 do
cyOut = cy < ct1 or cy > cb1
for cx = cl2, cr2 do
if cyOut or cx < cl1 or cx > cr1 then
addItemToCell(self, item, cx, cy)
end
end
end
end
local rect = self.rects[item]
rect.x, rect.y, rect.w, rect.h = x2,y2,w2,h2
end
end
function World:move(item, goalX, goalY, filter)
local actualX, actualY, cols, len = self:check(item, goalX, goalY, filter)
self:update(item, actualX, actualY)
return actualX, actualY, cols, len
end
function World:check(item, goalX, goalY, filter)
filter = filter or defaultFilter
local visited = {[item] = true}
local visitedFilter = function(itm, other)
if visited[other] then return false end
return filter(itm, other)
end
local cols, len = {}, 0
local x,y,w,h = self:getRect(item)
local projected_cols, projected_len = self:project(item, x,y,w,h, goalX,goalY, visitedFilter)
while projected_len > 0 do
local col = projected_cols[1]
len = len + 1
cols[len] = col
visited[col.other] = true
local response = getResponseByName(self, col.type)
goalX, goalY, projected_cols, projected_len = response(
self,
col,
x, y, w, h,
goalX, goalY,
visitedFilter
)
end
return goalX, goalY, cols, len
end
-- Public library functions
bump.newWorld = function(cellSize)
cellSize = cellSize or 64
assertIsPositiveNumber(cellSize, 'cellSize')
local world = setmetatable({
cellSize = cellSize,
rects = {},
rows = {},
nonEmptyCells = {},
responses = {}
}, World_mt)
world:addResponse('touch', touch)
world:addResponse('cross', cross)
world:addResponse('slide', slide)
world:addResponse('bounce', bounce)
return world
end
bump.rect = {
getNearestCorner = rect_getNearestCorner,
getSegmentIntersectionIndices = rect_getSegmentIntersectionIndices,
getDiff = rect_getDiff,
containsPoint = rect_containsPoint,
isIntersecting = rect_isIntersecting,
getSquareDistance = rect_getSquareDistance,
detectCollision = rect_detectCollision
}
bump.responses = {
touch = touch,
cross = cross,
slide = slide,
bounce = bounce
}
return bump

View file

@ -1,84 +0,0 @@
-- See: https://github.com/bungle/lua-resty-tsort
--
-- Copyright (c) 2016, Aapo Talvensaari
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without modification,
-- are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright notice, this
-- list of conditions and the following disclaimer.
--
-- * Redistributions in binary form must reproduce the above copyright notice, this
-- list of conditions and the following disclaimer in the documentation and/or
-- other materials provided with the distribution.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
-- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
local setmetatable = setmetatable
local pairs = pairs
local type = type
local function visit(k, n, m, s)
if m[k] == 0 then return 1 end
if m[k] == 1 then return end
m[k] = 0
local f = n[k]
for i=1, #f do
if visit(f[i], n, m, s) then return 1 end
end
m[k] = 1
s[#s+1] = k
end
local tsort = {}
tsort.__index = tsort
function tsort.new()
return setmetatable({ n = {} }, tsort)
end
function tsort:add(...)
local p = { ... }
local c = #p
if c == 0 then return self end
if c == 1 then
p = p[1]
if type(p) == "table" then
c = #p
else
p = { p }
end
end
local n = self.n
for i=1, c do
local f = p[i]
if n[f] == nil then n[f] = {} end
end
for i=2, c, 1 do
local f = p[i]
local t = p[i-1]
local o = n[f]
o[#o+1] = t
end
return self
end
function tsort:sort()
local n = self.n
local s = {}
local m = {}
for k in pairs(n) do
if m[k] == nil then
if visit(k, n, m, s) then
return nil, "There is a circular dependency in the graph. It is not possible to derive a topological sort."
end
end
end
return s
end
return tsort

View file

@ -1,737 +0,0 @@
--
-- lovebird
--
-- 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 socket = require "socket"
local lovebird = { _version = "0.4.3" }
lovebird.loadstring = loadstring or load
lovebird.inited = false
lovebird.host = "*"
lovebird.buffer = ""
lovebird.lines = {}
lovebird.connections = {}
lovebird.pages = {}
lovebird.wrapprint = true
lovebird.timestamp = true
lovebird.allowhtml = false
lovebird.echoinput = true
lovebird.port = 8000
lovebird.whitelist = { "127.0.0.1" }
lovebird.maxlines = 200
lovebird.updateinterval = .5
lovebird.pages["index"] = [[
<?lua
-- Handle console input
if req.parsedbody.input then
local str = req.parsedbody.input
if lovebird.echoinput then
lovebird.pushline({ type = 'input', str = str })
end
if str:find("^=") then
str = "print(" .. str:sub(2) .. ")"
end
xpcall(function() assert(lovebird.loadstring(str, "input"))() end,
lovebird.onerror)
end
?>
<!doctype html>
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=Edge"/>
<meta charset="utf-8">
<title>lovebird</title>
<style>
body {
margin: 0px;
font-size: 14px;
font-family: helvetica, verdana, sans;
background: #FFFFFF;
}
form {
margin-bottom: 0px;
}
.timestamp {
color: #909090;
padding-right: 4px;
}
.repeatcount {
color: #F0F0F0;
background: #505050;
font-size: 11px;
font-weight: bold;
text-align: center;
padding-left: 4px;
padding-right: 4px;
padding-top: 0px;
padding-bottom: 0px;
border-radius: 7px;
display: inline-block;
}
.errormarker {
color: #F0F0F0;
background: #8E0000;
font-size: 11px;
font-weight: bold;
text-align: center;
border-radius: 8px;
width: 17px;
padding-top: 0px;
padding-bottom: 0px;
display: inline-block;
}
.greybordered {
margin: 12px;
background: #F0F0F0;
border: 1px solid #E0E0E0;
border-radius: 3px;
}
.inputline {
font-family: mono, courier;
font-size: 13px;
color: #606060;
}
.inputline:before {
content: '\00B7\00B7\00B7';
padding-right: 5px;
}
.errorline {
color: #8E0000;
}
#header {
background: #101010;
height: 25px;
color: #F0F0F0;
padding: 9px
}
#title {
float: left;
font-size: 20px;
}
#title a {
color: #F0F0F0;
text-decoration: none;
}
#title a:hover {
color: #FFFFFF;
}
#version {
font-size: 10px;
}
#status {
float: right;
font-size: 14px;
padding-top: 4px;
}
#main a {
color: #000000;
text-decoration: none;
background: #E0E0E0;
border: 1px solid #D0D0D0;
border-radius: 3px;
padding-left: 2px;
padding-right: 2px;
display: inline-block;
}
#main a:hover {
background: #D0D0D0;
border: 1px solid #C0C0C0;
}
#console {
position: absolute;
top: 40px; bottom: 0px; left: 0px; right: 312px;
}
#input {
position: absolute;
margin: 10px;
bottom: 0px; left: 0px; right: 0px;
}
#inputbox {
width: 100%;
font-family: mono, courier;
font-size: 13px;
}
#output {
overflow-y: scroll;
position: absolute;
margin: 10px;
line-height: 17px;
top: 0px; bottom: 36px; left: 0px; right: 0px;
}
#env {
position: absolute;
top: 40px; bottom: 0px; right: 0px;
width: 300px;
}
#envheader {
padding: 5px;
background: #E0E0E0;
}
#envvars {
position: absolute;
left: 0px; right: 0px; top: 25px; bottom: 0px;
margin: 10px;
overflow-y: scroll;
font-size: 12px;
}
</style>
</head>
<body>
<div id="header">
<div id="title">
<a href="https://github.com/rxi/lovebird">lovebird</a>
<span id="version"><?lua echo(lovebird._version) ?></span>
</div>
<div id="status"></div>
</div>
<div id="main">
<div id="console" class="greybordered">
<div id="output"> <?lua echo(lovebird.buffer) ?> </div>
<div id="input">
<form method="post"
onkeydown="return onInputKeyDown(event);"
onsubmit="onInputSubmit(); return false;">
<input id="inputbox" name="input" type="text"
autocomplete="off"></input>
</form>
</div>
</div>
<div id="env" class="greybordered">
<div id="envheader"></div>
<div id="envvars"></div>
</div>
</div>
<script>
document.getElementById("inputbox").focus();
var changeFavicon = function(href) {
var old = document.getElementById("favicon");
if (old) document.head.removeChild(old);
var link = document.createElement("link");
link.id = "favicon";
link.rel = "shortcut icon";
link.href = href;
document.head.appendChild(link);
}
var truncate = function(str, len) {
if (str.length <= len) return str;
return str.substring(0, len - 3) + "...";
}
var geturl = function(url, onComplete, onFail) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState != 4) return;
if (req.status == 200) {
if (onComplete) onComplete(req.responseText);
} else {
if (onFail) onFail(req.responseText);
}
}
url += (url.indexOf("?") > -1 ? "&_=" : "?_=") + Math.random();
req.open("GET", url, true);
req.send();
}
var divContentCache = {}
var updateDivContent = function(id, content) {
if (divContentCache[id] != content) {
document.getElementById(id).innerHTML = content;
divContentCache[id] = content
return true;
}
return false;
}
var onInputSubmit = function() {
var b = document.getElementById("inputbox");
var req = new XMLHttpRequest();
req.open("POST", "/", true);
req.send("input=" + encodeURIComponent(b.value));
/* Do input history */
if (b.value && inputHistory[0] != b.value) {
inputHistory.unshift(b.value);
}
inputHistory.index = -1;
/* Reset */
b.value = "";
refreshOutput();
}
/* Input box history */
var inputHistory = [];
inputHistory.index = 0;
var onInputKeyDown = function(e) {
var key = e.which || e.keyCode;
if (key != 38 && key != 40) return true;
var b = document.getElementById("inputbox");
if (key == 38 && inputHistory.index < inputHistory.length - 1) {
/* Up key */
inputHistory.index++;
}
if (key == 40 && inputHistory.index >= 0) {
/* Down key */
inputHistory.index--;
}
b.value = inputHistory[inputHistory.index] || "";
b.selectionStart = b.value.length;
return false;
}
/* Output buffer and status */
var refreshOutput = function() {
geturl("/buffer", function(text) {
updateDivContent("status", "connected &#9679;");
if (updateDivContent("output", text)) {
var div = document.getElementById("output");
div.scrollTop = div.scrollHeight;
}
/* Update favicon */
changeFavicon("data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAP1BMVEUAAAAAAAAAAAD////19fUO"+
"Dg7v7+/h4eGzs7MlJSUeHh7n5+fY2NjJycnGxsa3t7eioqKfn5+QkJCHh4d+fn7zU+b5AAAAAnRS"+
"TlPlAFWaypEAAABRSURBVBjTfc9HDoAwDERRQ+w0ern/WQkZaUBC4e/mrWzppH9VJjbjZg1Ii2rM"+
"DyR1JZ8J0dVWggIGggcEwgbYCRbuPRqgyjHNpzUP+39GPu9fgloC5L9DO0sAAAAASUVORK5CYII="
);
},
function(text) {
updateDivContent("status", "disconnected &#9675;");
/* Update favicon */
changeFavicon("data:image/png;base64," +
"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAYFBMVEUAAAAAAAAAAADZ2dm4uLgM"+
"DAz29vbz8/Pv7+/h4eHIyMiwsLBtbW0lJSUeHh4QEBDn5+fS0tLDw8O0tLSioqKfn5+QkJCHh4d+"+
"fn5ycnJmZmZgYGBXV1dLS0tFRUUGBgZ0He44AAAAAnRSTlPlAFWaypEAAABeSURBVBjTfY9HDoAw"+
"DAQD6Z3ey/9/iXMxkVDYw0g7F3tJReosUKHnwY4pCM+EtOEVXrb7wVRA0dMbaAcUwiVeDQq1Jp4a"+
"xUg5kE0ooqZu68Di2Tgbs/DiY/9jyGf+AyFKBAK7KD2TAAAAAElFTkSuQmCC"
);
});
}
setInterval(refreshOutput,
<?lua echo(lovebird.updateinterval) ?> * 1000);
/* Environment variable view */
var envPath = "";
var refreshEnv = function() {
geturl("/env.json?p=" + envPath, function(text) {
var json = eval("(" + text + ")");
/* Header */
var html = "<a href='#' onclick=\"setEnvPath('')\">env</a>";
var acc = "";
var p = json.path != "" ? json.path.split(".") : [];
for (var i = 0; i < p.length; i++) {
acc += "." + p[i];
html += " <a href='#' onclick=\"setEnvPath('" + acc + "')\">" +
truncate(p[i], 10) + "</a>";
}
updateDivContent("envheader", html);
/* Handle invalid table path */
if (!json.valid) {
updateDivContent("envvars", "Bad path");
return;
}
/* Variables */
var html = "<table>";
for (var i = 0; json.vars[i]; i++) {
var x = json.vars[i];
var fullpath = (json.path + "." + x.key).replace(/^\./, "");
var k = truncate(x.key, 15);
if (x.type == "table") {
k = "<a href='#' onclick=\"setEnvPath('" + fullpath + "')\">" +
k + "</a>";
}
var v = "<a href='#' onclick=\"insertVar('" +
fullpath.replace(/\.(-?[0-9]+)/g, "[$1]") +
"');\">" + x.value + "</a>"
html += "<tr><td>" + k + "</td><td>" + v + "</td></tr>";
}
html += "</table>";
updateDivContent("envvars", html);
});
}
var setEnvPath = function(p) {
envPath = p;
refreshEnv();
}
var insertVar = function(p) {
var b = document.getElementById("inputbox");
b.value += p;
b.focus();
}
setInterval(refreshEnv, <?lua echo(lovebird.updateinterval) ?> * 1000);
</script>
</body>
</html>
]]
lovebird.pages["buffer"] = [[ <?lua echo(lovebird.buffer) ?> ]]
lovebird.pages["env.json"] = [[
<?lua
local t = _G
local p = req.parsedurl.query.p or ""
p = p:gsub("%.+", "."):match("^[%.]*(.*)[%.]*$")
if p ~= "" then
for x in p:gmatch("[^%.]+") do
t = t[x] or t[tonumber(x)]
-- Return early if path does not exist
if type(t) ~= "table" then
echo('{ "valid": false, "path": ' .. string.format("%q", p) .. ' }')
return
end
end
end
?>
{
"valid": true,
"path": "<?lua echo(p) ?>",
"vars": [
<?lua
local keys = {}
for k in pairs(t) do
if type(k) == "number" or type(k) == "string" then
table.insert(keys, k)
end
end
table.sort(keys, lovebird.compare)
for _, k in pairs(keys) do
local v = t[k]
?>
{
"key": "<?lua echo(k) ?>",
"value": <?lua echo(
string.format("%q",
lovebird.truncate(
lovebird.htmlescape(
tostring(v)), 26))) ?>,
"type": "<?lua echo(type(v)) ?>",
},
<?lua end ?>
]
}
]]
function lovebird.init()
-- Init server
lovebird.server = assert(socket.bind(lovebird.host, lovebird.port))
lovebird.addr, lovebird.port = lovebird.server:getsockname()
lovebird.server:settimeout(0)
-- Wrap print
lovebird.origprint = print
if lovebird.wrapprint then
local oldprint = print
print = function(...)
oldprint(...)
lovebird.print(...)
end
end
-- Compile page templates
for k, page in pairs(lovebird.pages) do
lovebird.pages[k] = lovebird.template(page, "lovebird, req",
"pages." .. k)
end
lovebird.inited = true
end
function lovebird.template(str, params, chunkname)
params = params and ("," .. params) or ""
local f = function(x) return string.format(" echo(%q)", x) end
str = ("?>"..str.."<?lua"):gsub("%?>(.-)<%?lua", f)
str = "local echo " .. params .. " = ..." .. str
local fn = assert(lovebird.loadstring(str, chunkname))
return function(...)
local output = {}
local echo = function(str) table.insert(output, str) end
fn(echo, ...)
return table.concat(lovebird.map(output, tostring))
end
end
function lovebird.map(t, fn)
local res = {}
for k, v in pairs(t) do res[k] = fn(v) end
return res
end
function lovebird.trace(...)
local str = "[lovebird] " .. table.concat(lovebird.map({...}, tostring), " ")
print(str)
if not lovebird.wrapprint then lovebird.print(str) end
end
function lovebird.unescape(str)
local f = function(x) return string.char(tonumber("0x"..x)) end
return (str:gsub("%+", " "):gsub("%%(..)", f))
end
function lovebird.parseurl(url)
local res = {}
res.path, res.search = url:match("/([^%?]*)%??(.*)")
res.query = {}
for k, v in res.search:gmatch("([^&^?]-)=([^&^#]*)") do
res.query[k] = lovebird.unescape(v)
end
return res
end
local htmlescapemap = {
["<"] = "&lt;",
["&"] = "&amp;",
['"'] = "&quot;",
["'"] = "&#039;",
}
function lovebird.htmlescape(str)
return ( str:gsub("[<&\"']", htmlescapemap) )
end
function lovebird.truncate(str, len)
if #str <= len then
return str
end
return str:sub(1, len - 3) .. "..."
end
function lovebird.compare(a, b)
local na, nb = tonumber(a), tonumber(b)
if na then
if nb then return na < nb end
return false
elseif nb then
return true
end
return tostring(a) < tostring(b)
end
function lovebird.checkwhitelist(addr)
if lovebird.whitelist == nil then return true end
for _, a in pairs(lovebird.whitelist) do
local ptn = "^" .. a:gsub("%.", "%%."):gsub("%*", "%%d*") .. "$"
if addr:match(ptn) then return true end
end
return false
end
function lovebird.clear()
lovebird.lines = {}
lovebird.buffer = ""
end
function lovebird.pushline(line)
line.time = os.time()
line.count = 1
table.insert(lovebird.lines, line)
if #lovebird.lines > lovebird.maxlines then
table.remove(lovebird.lines, 1)
end
lovebird.recalcbuffer()
end
function lovebird.recalcbuffer()
local function doline(line)
local str = line.str
if not lovebird.allowhtml then
str = lovebird.htmlescape(line.str):gsub("\n", "<br>")
end
if line.type == "input" then
str = '<span class="inputline">' .. str .. '</span>'
else
if line.type == "error" then
str = '<span class="errormarker">!</span> ' .. str
str = '<span class="errorline">' .. str .. '</span>'
end
if line.count > 1 then
str = '<span class="repeatcount">' .. line.count .. '</span> ' .. str
end
if lovebird.timestamp then
str = os.date('<span class="timestamp">%H:%M:%S</span> ', line.time) ..
str
end
end
return str
end
lovebird.buffer = table.concat(lovebird.map(lovebird.lines, doline), "<br>")
end
function lovebird.print(...)
local t = {}
for i = 1, select("#", ...) do
table.insert(t, tostring(select(i, ...)))
end
local str = table.concat(t, " ")
local last = lovebird.lines[#lovebird.lines]
if last and str == last.str then
-- Update last line if this line is a duplicate of it
last.time = os.time()
last.count = last.count + 1
lovebird.recalcbuffer()
else
-- Create new line
lovebird.pushline({ type = "output", str = str })
end
end
function lovebird.onerror(err)
lovebird.pushline({ type = "error", str = err })
if lovebird.wrapprint then
lovebird.origprint("[lovebird] ERROR: " .. err)
end
end
function lovebird.onrequest(req, client)
local page = req.parsedurl.path
page = page ~= "" and page or "index"
-- Handle "page not found"
if not lovebird.pages[page] then
return "HTTP/1.1 404\r\nContent-Length: 8\r\n\r\nBad page"
end
-- Handle page
local str
xpcall(function()
local data = lovebird.pages[page](lovebird, req)
local contenttype = "text/html"
if string.match(page, "%.json$") then
contenttype = "application/json"
end
str = "HTTP/1.1 200 OK\r\n" ..
"Content-Type: " .. contenttype .. "\r\n" ..
"Content-Length: " .. #data .. "\r\n" ..
"\r\n" .. data
end, lovebird.onerror)
return str
end
function lovebird.receive(client, pattern)
while 1 do
local data, msg = client:receive(pattern)
if not data then
if msg == "timeout" then
-- Wait for more data
coroutine.yield(true)
else
-- Disconnected -- yielding nil means we're done
coroutine.yield(nil)
end
else
return data
end
end
end
function lovebird.send(client, data)
local idx = 1
while idx < #data do
local res, msg = client:send(data, idx)
if not res and msg == "closed" then
-- Handle disconnect
coroutine.yield(nil)
else
idx = idx + res
coroutine.yield(true)
end
end
end
function lovebird.onconnect(client)
-- Create request table
local requestptn = "(%S*)%s*(%S*)%s*(%S*)"
local req = {}
req.socket = client
req.addr, req.port = client:getsockname()
req.request = lovebird.receive(client, "*l")
req.method, req.url, req.proto = req.request:match(requestptn)
req.headers = {}
while 1 do
local line, msg = lovebird.receive(client, "*l")
if not line or #line == 0 then break end
local k, v = line:match("(.-):%s*(.*)$")
req.headers[k] = v
end
if req.headers["Content-Length"] then
req.body = lovebird.receive(client, req.headers["Content-Length"])
end
-- Parse body
req.parsedbody = {}
if req.body then
for k, v in req.body:gmatch("([^&]-)=([^&^#]*)") do
req.parsedbody[k] = lovebird.unescape(v)
end
end
-- Parse request line's url
req.parsedurl = lovebird.parseurl(req.url)
-- Handle request; get data to send and send
local data = lovebird.onrequest(req)
lovebird.send(client, data)
-- Clear up
client:close()
end
function lovebird.update()
if not lovebird.inited then lovebird.init() end
-- Handle new connections
while 1 do
-- Accept new connections
local client = lovebird.server:accept()
if not client then break end
client:settimeout(0)
local addr = client:getsockname()
if lovebird.checkwhitelist(addr) then
-- Connection okay -- create and add coroutine to set
local conn = coroutine.wrap(function()
xpcall(function() lovebird.onconnect(client) end, function() end)
end)
lovebird.connections[conn] = true
else
-- Reject connection not on whitelist
lovebird.trace("got non-whitelisted connection attempt: ", addr)
client:close()
end
end
-- Handle existing connections
for conn in pairs(lovebird.connections) do
-- Resume coroutine, remove if it has finished
local status = conn()
if status == nil then
lovebird.connections[conn] = nil
end
end
end
return lovebird

View file

@ -1,16 +0,0 @@
local Filesystem = {}
function Filesystem.exists(filepath)
local info = love.filesystem.getInfo( filepath )
local exists = false
if (info == nil) then
exists = false
else
exists = true
end
return exists
end
return Filesystem

View file

@ -1,64 +0,0 @@
local Graphics = {}
function Graphics.resetColor()
love.graphics.setColor(1,1,1,1)
end
function Graphics.box(x, y, w, h)
local x = math.floor(x)
local y = math.floor(y)
local w = math.floor(w)
local h = math.floor(h)
local a = a or 1
local r, g, b, a = love.graphics.getColor( )
love.graphics.setColor(r, g, b, 0.3 * a)
love.graphics.rectangle("fill", x, y, w, h)
love.graphics.setColor(r, g, b, a)
love.graphics.rectangle("line", x, y, w, h)
end
function Graphics.print(text, x, y, align, r, sx, sy, ox, oy, kx, ky)
local width
local font = love.graphics.getFont()
width = font:getWidth(text)
if align == "center" then
width = (width/2)
elseif align == "right" then
width = width
else
width = 0
end
love.graphics.print(text, x - (width), y, r, sx, sy, ox, oy, kx, ky)
end
function Graphics.printWithSpacing(text, spacing, align, x, y, r, sx, sy, ox, oy, kx, ky)
-- DO NOT USE THIS FUNCTION IN A "UPDATE" FUNCTION !
-- it's pretty heavy to use as it use a loop to get every character in a text
local font = love.graphics.getFont()
local xx = 0
local lenght = string.len(text)
local basewidth = font:getWidth(text)
local width = basewidth + (spacing * lenght)
if align == "center" then
width = (width/2)
elseif align == "right" then
width = width
else
width = 0
end
for i=1, lenght do
local char = string.sub(text, i, i)
pos = math.floor(x + xx - width)
love.graphics.print(char, pos, y)
xx = xx + font:getWidth(char) + spacing
end
end
return Graphics

View file

@ -1,7 +0,0 @@
local cwd = (...):gsub('%.init$', '') .. "."
return {
math = require(cwd .. "math"),
graphics = require(cwd .. "graphics"),
filesystem = require(cwd .. "filesystem")
}

View file

@ -1,104 +0,0 @@
local Math = {}
function Math.wrap(x, startnumber, endnumber)
local delta = endnumber - startnumber
if delta < 0 then
err("endnumber must be larger than startnumber")
end
while x >= endnumber do
x = x - delta
end
while x < startnumber do
x = x + delta
end
return x
end
function Math.sign(x)
if (x < 0) then
return -1
elseif (x > 0) then
return 1
else
return 0
end
end
function Math.round(num)
return math.floor(num + 0.5)
end
function Math.vector(x1, y1, x2, y2)
local vecx, vecy
vecx = x2 - x1
vexy = y2 - y1
return vecx, vecy
end
function Math.getMiddlePoint(x1, y1, x2, y2)
local newx, newy, vecx, vecy
vecx = math.max(x1, x2) - math.min(x1, x2)
vecy = math.max(y1, y2) - math.min(y1, y2)
newx = math.min(x1, x2) + (vecx / 2)
newy = math.min(y1, y2) + (vecy / 2)
return newx, newy
end
function Math.pointDistance(x1, y1, x2, y2)
local vecx, vecy
vecx = math.max(x1, x2) - math.min(x1, x2)
vecy = math.max(y1, y2) - math.min(y1, y2)
return math.sqrt(vecx^2 + vecy^2)
end
function Math.pointDirection(x1,y1,x2,y2)
local vecx, vecy, angle
vecy = y2 - y1
vecx = x2 - x1
angle = math.atan2(vecy, vecx)
return angle
end
function Math.numberToString(x, length)
local length = length or 1
local string = ""
local x = x
if (x >= math.pow(10, length)) then
x = unitsNumber*10 - 1
string = string .. x
else
for i=1, (length-1) do
if (x < math.pow(10, length-i)) then
string = string .. "0"
end
end
string = string .. x
end
return string
end
function Math.floorCoord(x, y)
return math.floor(x), math.floor(y)
end
function Math.pixeliseCoord(x, y, factor)
x, y = Math.floorCoord(x / factor, y / factor)
x = x * factor
y = y * factor
return x, y
end
return Math