feat: remove libs
This commit is contained in:
parent
66b77d59ed
commit
bc8d61728b
18 changed files with 0 additions and 4687 deletions
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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})
|
|
|
@ -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})
|
|
|
@ -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
|
|
|
@ -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})
|
|
|
@ -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})
|
|
|
@ -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,
|
|
||||||
}
|
|
|
@ -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})
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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 ●");
|
|
||||||
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 ○");
|
|
||||||
/* 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 = {
|
|
||||||
["<"] = "<",
|
|
||||||
["&"] = "&",
|
|
||||||
['"'] = """,
|
|
||||||
["'"] = "'",
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -1,7 +0,0 @@
|
||||||
local cwd = (...):gsub('%.init$', '') .. "."
|
|
||||||
|
|
||||||
return {
|
|
||||||
math = require(cwd .. "math"),
|
|
||||||
graphics = require(cwd .. "graphics"),
|
|
||||||
filesystem = require(cwd .. "filesystem")
|
|
||||||
}
|
|
|
@ -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
|
|
Loading…
Reference in a new issue