mirror of
https://github.com/1bardesign/batteries.git
synced 2024-11-10 02:31:48 +00:00
initial commit
This commit is contained in:
commit
2f470cf7c7
407
functional.lua
Normal file
407
functional.lua
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
--[[
|
||||||
|
functional programming facilities
|
||||||
|
]]
|
||||||
|
|
||||||
|
--collect all keys of a table into a sequence
|
||||||
|
function table.keys(t)
|
||||||
|
local r = {}
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
table.insert(r, k)
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--collect all values of a table into a sequence
|
||||||
|
--(shallow copy if it's already a sequence)
|
||||||
|
function table.values(t)
|
||||||
|
local r = sequence:new()
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
table.insert(r, v)
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--simple sequential iteration, f is called for all elements of t
|
||||||
|
--f can return non-nil to break the loop (and return the value)
|
||||||
|
function table.foreach(t, f)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
local r = f(v, i)
|
||||||
|
if r ~= nil then
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--performs a left to right reduction of t using f, with o as the initial value
|
||||||
|
-- reduce({1, 2, 3}, f, 0) -> f(f(f(0, 1), 2), 3)
|
||||||
|
-- (but performed iteratively, so no stack smashing)
|
||||||
|
function table.reduce(t, f, o)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
o = f(o, v)
|
||||||
|
end
|
||||||
|
return o
|
||||||
|
end
|
||||||
|
|
||||||
|
--maps a sequence {a, b, c} -> {f(a), f(b), f(c)}
|
||||||
|
-- (automatically drops any nils due to table.insert, which can be used to simultaneously map and filter)
|
||||||
|
function table.map(t, f)
|
||||||
|
local r = {}
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
local mapped = f(v, i)
|
||||||
|
if mapped ~= nil then
|
||||||
|
table.insert(r, mapped)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--filters a sequence
|
||||||
|
function table.filter(t, f)
|
||||||
|
local r = {}
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if f(v, i) then
|
||||||
|
table.insert(r, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--partitions a sequence based on filter criteria
|
||||||
|
function table.partition(t, f)
|
||||||
|
local a = {}
|
||||||
|
local b = {}
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if f(v, i) then
|
||||||
|
table.insert(a, v)
|
||||||
|
else
|
||||||
|
table.insert(b, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return a, b
|
||||||
|
end
|
||||||
|
|
||||||
|
--zips two sequences together into a new table, based on another function
|
||||||
|
--iteration limited by min(#t1, #t2)
|
||||||
|
--function receives arguments (t1, t2, i)
|
||||||
|
--nil results ignored
|
||||||
|
function table.zip(t1, t2, f)
|
||||||
|
local ret = {}
|
||||||
|
local limit = math.min(#t2, #t2)
|
||||||
|
for i=1, limit do
|
||||||
|
local v1 = t1[i]
|
||||||
|
local v2 = t2[i]
|
||||||
|
local zipped = f(v1, v2, i)
|
||||||
|
if zipped ~= nil then
|
||||||
|
table.insert(ret, zipped)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
--return a copy of a sequence with all duplicates removed
|
||||||
|
-- causes a little "extra" gc churn; one table and one closure
|
||||||
|
-- as well as the copied deduped table
|
||||||
|
function table.dedupe(t)
|
||||||
|
local seen = {}
|
||||||
|
return table.filter(t, function(v)
|
||||||
|
if seen[v] then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
seen[v] = true
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--append sequence t2 into t1, modifying t1
|
||||||
|
function table.append_inplace(t1, t2)
|
||||||
|
table.foreach(t2, function(v)
|
||||||
|
table.insert(t1, v)
|
||||||
|
end)
|
||||||
|
return t1
|
||||||
|
end
|
||||||
|
|
||||||
|
--return a new sequence with the elements of both t1 and t2
|
||||||
|
function table.append(t1, t2)
|
||||||
|
local r = {}
|
||||||
|
append_inplace(r, t1)
|
||||||
|
append_inplace(r, t2)
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
--copy a table
|
||||||
|
-- if deep specified:
|
||||||
|
-- calls copy method of member directly if it exists
|
||||||
|
-- and recurses into all "normal" table children
|
||||||
|
function table.copy(t, deep)
|
||||||
|
local r = {}
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if deep and type(v) == "table" then
|
||||||
|
if type(v.copy) == "function" then
|
||||||
|
v = v:copy()
|
||||||
|
else
|
||||||
|
v = table.copy(v, deep)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
r[k] = v
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
--common queries and reductions
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
--true if any element of the table matches f
|
||||||
|
function table.any(t, f)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if f(v) then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--true if no element of the table matches f
|
||||||
|
function table.none(t, f)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if f(v) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--true if all elements of the table match f
|
||||||
|
function table.all(t, f)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if not f(v) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--counts the elements of t that match f
|
||||||
|
function table.count(t, f)
|
||||||
|
local c = 0
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if f(v) then
|
||||||
|
c = c + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return c
|
||||||
|
end
|
||||||
|
|
||||||
|
--true if the table contains element e
|
||||||
|
function table.contains(t, e)
|
||||||
|
for i, v in ipairs(t) do
|
||||||
|
if v == e then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--return the numeric sum of all elements of t
|
||||||
|
function table.sum(t)
|
||||||
|
return table.reduce(t, function(a, b)
|
||||||
|
return a + b
|
||||||
|
end, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--return the numeric mean of all elements of t
|
||||||
|
function table.mean(t)
|
||||||
|
local len = #t
|
||||||
|
if len == 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
return table.sum(t) / len
|
||||||
|
end
|
||||||
|
|
||||||
|
--return the minimum and maximum of t in one pass
|
||||||
|
function table.minmax(t)
|
||||||
|
local a = table.reduce(t, function(a, b)
|
||||||
|
a.min = a.min and math.min(a.min, b) or b
|
||||||
|
a.max = a.max and math.max(a.max, b) or b
|
||||||
|
return a
|
||||||
|
end, {})
|
||||||
|
if a.min == nil then
|
||||||
|
a.min = 0
|
||||||
|
a.max = 0
|
||||||
|
end
|
||||||
|
return a.min, a.max
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.max(t)
|
||||||
|
local min, max = table.minmax(t)
|
||||||
|
return max
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.min(t)
|
||||||
|
local min, max = table.minmax(t)
|
||||||
|
return min
|
||||||
|
end
|
||||||
|
|
||||||
|
--return the element of the table that results in the greatest numeric value
|
||||||
|
--(function receives element and key respectively, table evaluated in pairs order)
|
||||||
|
function table.find_best(t, f)
|
||||||
|
local current = nil
|
||||||
|
local current_best = -math.huge
|
||||||
|
for k,e in pairs(t) do
|
||||||
|
local v = f(e, k)
|
||||||
|
if v > current_best then
|
||||||
|
current_best = v
|
||||||
|
current = e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return current
|
||||||
|
end
|
||||||
|
|
||||||
|
--return the element of the table that results in the value nearest to the passed value
|
||||||
|
function table.find_nearest(t, f, v)
|
||||||
|
return table.find_best(t, function(e)
|
||||||
|
return -math.abs(f(e) - v)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--return the first element of the table that results in a true filter
|
||||||
|
function table.find_match(t, f)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if f(v) then
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
--sequence - functional wrapper for ordered tables
|
||||||
|
-----------------------------------------------------------
|
||||||
|
sequence = {}
|
||||||
|
|
||||||
|
sequence.mt = {__index = sequence}
|
||||||
|
|
||||||
|
--upgrade a table into a functional sequence
|
||||||
|
function sequence:new(t)
|
||||||
|
return setmetatable(t or {}, sequence.mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
--import table functions to sequence as-is
|
||||||
|
sequence.insert = table.insert
|
||||||
|
sequence.remove = table.remove
|
||||||
|
sequence.concat = table.concat
|
||||||
|
sequence.join = table.concat --alias
|
||||||
|
|
||||||
|
--sorting
|
||||||
|
sequence.sort = table.stable_sort --(default stable)
|
||||||
|
sequence.stable_sort = table.stable_sort
|
||||||
|
sequence.unstable_sort = table.sort
|
||||||
|
|
||||||
|
--import functional interface to sequence
|
||||||
|
function sequence:keys()
|
||||||
|
return sequence:new(table.keys(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:values()
|
||||||
|
return sequence:new(table.values(self))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:foreach(f)
|
||||||
|
return table.foreach(self, f)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:reduce(f, o)
|
||||||
|
return table.foreach(self, f, o)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:map(f)
|
||||||
|
return sequence:new(table.map(self, f))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:filter(f)
|
||||||
|
return sequence:new(table.filter(self, f))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:partition(f)
|
||||||
|
local a, b = table.partition(self, f)
|
||||||
|
return sequence:new(a), sequence:new(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:zip(other, f)
|
||||||
|
return sequence:new(table.zip(self, other, f))
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:dedupe()
|
||||||
|
return table.dedupe(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:append_inplace(other)
|
||||||
|
return table.append_inplace(self, other)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:append(other)
|
||||||
|
return sequence:new():append_inplace(self):append_inplace(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
function sequence:copy(deep)
|
||||||
|
return sequence:new(table.copy(self, deep))
|
||||||
|
end
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
sequence.any = table.any
|
||||||
|
sequence.none = table.none
|
||||||
|
sequence.all = table.all
|
||||||
|
sequence.count = table.count
|
||||||
|
sequence.contains = table.contains
|
||||||
|
sequence.sum = table.sum
|
||||||
|
sequence.mean = table.mean
|
||||||
|
sequence.minmax = table.minmax
|
||||||
|
sequence.max = table.max
|
||||||
|
sequence.min = table.min
|
||||||
|
sequence.find_best = table.find_best
|
||||||
|
sequence.find_nearest = table.find_nearest
|
||||||
|
sequence.find_match = table.find_match
|
||||||
|
|
||||||
|
--generate a mapping from unique values to plain numbers
|
||||||
|
--useful for arbitrarily ordering things that don't have
|
||||||
|
--a natural ordering implied (eg textures for batching)
|
||||||
|
|
||||||
|
unique_mapping = {}
|
||||||
|
unique_mapping.mt = {
|
||||||
|
__index = unique_mapping,
|
||||||
|
__mode = "kv", --weak refs
|
||||||
|
}
|
||||||
|
|
||||||
|
--(used as storage for non-weak data)
|
||||||
|
local _MAP_VARS = setmetatable({}, {
|
||||||
|
__mode = "k" --only keys are weak
|
||||||
|
})
|
||||||
|
|
||||||
|
function unique_mapping:new()
|
||||||
|
local r = setmetatable({}, unique_mapping.mt)
|
||||||
|
--set up the actual vars
|
||||||
|
_MAP_VARS[r] = {
|
||||||
|
current_index = 0,
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
function unique_mapping:_increment()
|
||||||
|
local vars = _MAP_VARS[self]
|
||||||
|
vars.current_index = vars.current_index + 1
|
||||||
|
return vars.current_index
|
||||||
|
end
|
||||||
|
|
||||||
|
function unique_mapping:map(value)
|
||||||
|
local val = self[value]
|
||||||
|
if val then
|
||||||
|
return val
|
||||||
|
end
|
||||||
|
local i = self:_increment()
|
||||||
|
self[value] = i
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
28
init.lua
Normal file
28
init.lua
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
--[[
|
||||||
|
core modules
|
||||||
|
|
||||||
|
if required as the "entire library" (ie by this file), puts everything into
|
||||||
|
global namespace as it'll presumably be commonly used
|
||||||
|
|
||||||
|
if not, several of the modules work as "normal" modules and return a table
|
||||||
|
for local-friendly use
|
||||||
|
]]
|
||||||
|
|
||||||
|
local path = ...
|
||||||
|
local function relative_file(p)
|
||||||
|
return table.concat({path, p}, ".")
|
||||||
|
end
|
||||||
|
|
||||||
|
require(relative_file("oo"))
|
||||||
|
|
||||||
|
require(relative_file("math"))
|
||||||
|
|
||||||
|
require(relative_file("table"))
|
||||||
|
require(relative_file("stable_sort"))
|
||||||
|
|
||||||
|
require(relative_file("functional"))
|
||||||
|
|
||||||
|
vec2 = require(relative_file("vec2"))
|
||||||
|
intersect = require(relative_file("intersect"))
|
||||||
|
|
||||||
|
state_machine = require(relative_file("state_machine"))
|
389
intersect.lua
Normal file
389
intersect.lua
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
--[[
|
||||||
|
geometric intersection routines
|
||||||
|
from simple point tests to shape vs shape tests
|
||||||
|
|
||||||
|
options for boolean or minimum separating vector results
|
||||||
|
|
||||||
|
continuous sweeps (where provided) also return the
|
||||||
|
time-domain position of first intersection
|
||||||
|
|
||||||
|
TODO: refactor storage to be pooled rather than fully local
|
||||||
|
so these functions can be reentrant
|
||||||
|
]]
|
||||||
|
|
||||||
|
local intersect = {}
|
||||||
|
|
||||||
|
--epsilon for collisions
|
||||||
|
local COLLIDE_EPS = 1e-6
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- circles
|
||||||
|
|
||||||
|
function intersect.circle_point_overlap(pos, rad, v)
|
||||||
|
return pos:distance_squared(v) < rad * rad
|
||||||
|
end
|
||||||
|
|
||||||
|
function intersect.circle_circle_overlap(a_pos, a_rad, b_pos, b_rad)
|
||||||
|
local rad = a_rad + b_rad
|
||||||
|
return a_pos:distance_squared(b_pos) < rad * rad
|
||||||
|
end
|
||||||
|
|
||||||
|
local _ccc_delta = vec2:zero()
|
||||||
|
function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into)
|
||||||
|
--get delta
|
||||||
|
_ccc_delta:vset(a_pos):vsubi(b_pos)
|
||||||
|
--squared threshold
|
||||||
|
local rad = a_rad + b_rad
|
||||||
|
local dist = _ccc_delta:length_squared()
|
||||||
|
if dist < rad * rad then
|
||||||
|
if dist == 0 then
|
||||||
|
--singular case; just resolve vertically
|
||||||
|
dist = 1
|
||||||
|
_ccc_delta:sset(0,1)
|
||||||
|
else
|
||||||
|
--get actual distance
|
||||||
|
dist = math.sqrt(dist)
|
||||||
|
end
|
||||||
|
--allocate if needed
|
||||||
|
if into == nil then
|
||||||
|
into = vec2:zero()
|
||||||
|
end
|
||||||
|
--normalise, scale to separating distance
|
||||||
|
into:vset(_ccc_delta):sdiv(dist):smuli(rad - dist)
|
||||||
|
return into
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- line segments
|
||||||
|
-- todo: separate double-sided, one-sided, and pull-through (along normal) collisions?
|
||||||
|
|
||||||
|
--vector from line seg to point
|
||||||
|
function intersect._line_to_point(a_start, a_end, b_pos, into)
|
||||||
|
if into == nil then into = vec2:zero() end
|
||||||
|
--direction of line
|
||||||
|
into:vset(a_end):vsub(a_start)
|
||||||
|
--detect degenerate case
|
||||||
|
if into:length_squared() <= COLLIDE_EPS then
|
||||||
|
return intersect.circle_circle_collide(a_start, a_rad, b_pos, b_rad)
|
||||||
|
end
|
||||||
|
--solve for factor along line
|
||||||
|
local dx = (b_pos.x - a_start.x) * (a_end.x - a_start.x)
|
||||||
|
local dy = (b_pos.y - a_start.y) * (a_end.y - a_start.y)
|
||||||
|
local u = (dx + dy) / into:length_squared()
|
||||||
|
--clamp onto segment
|
||||||
|
u = math.clamp01(u)
|
||||||
|
--get the displacement to the nearest point (invalidate direction)
|
||||||
|
into:smuli(u):vaddi(a_start):vsubi(b_pos)
|
||||||
|
return into
|
||||||
|
end
|
||||||
|
|
||||||
|
--line displacement vector from separation vector
|
||||||
|
function intersect._line_displacement_to_sep(a_start, a_end, separation, total_rad)
|
||||||
|
local distance = separation:normalisei_len()
|
||||||
|
local sep = distance - total_rad
|
||||||
|
if sep <= 0 then
|
||||||
|
if distance <= COLLIDE_EPS then
|
||||||
|
--point intersecting the line; push out along normal
|
||||||
|
separation:vset(a_end):vsub(a_start):normalisei():rot90li()
|
||||||
|
else
|
||||||
|
separation:smuli(-sep)
|
||||||
|
end
|
||||||
|
return separation
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--collide a line segment with a circle
|
||||||
|
function intersect.line_circle_collide(a_start, a_end, a_rad, b_pos, b_rad, into)
|
||||||
|
into = intersect._line_to_point(a_start, a_end, b_pos, into)
|
||||||
|
return intersect._line_displacement_to_sep(a_start, a_end, into, a_rad + b_rad)
|
||||||
|
end
|
||||||
|
|
||||||
|
--collide 2 line segments
|
||||||
|
function intersect.line_line_collide(a_start, a_end, a_rad, b_start, b_end, b_rad, into)
|
||||||
|
--segment directions from start points
|
||||||
|
local a_dir = a_end:vsub(a_start)
|
||||||
|
local b_dir = b_end:vsub(b_start)
|
||||||
|
|
||||||
|
--detect degenerate cases
|
||||||
|
local a_degen = a_dir:length_squared() <= COLLIDE_EPS
|
||||||
|
local b_degen = a_dir:length_squared() <= COLLIDE_EPS
|
||||||
|
if a_degen and b_degen then
|
||||||
|
--actually just circles
|
||||||
|
return intersect.circle_circle_collide(a_start, a_rad, b_start, b_rad, into)
|
||||||
|
elseif a_degen then
|
||||||
|
-- a is just circle; annoying, need reversed msv
|
||||||
|
local collided = intersect.line_circle_collide(b_start, b_end, b_rad, a_start, a_rad, into)
|
||||||
|
if collided then
|
||||||
|
collided:smuli(-1)
|
||||||
|
end
|
||||||
|
return collided
|
||||||
|
elseif b_degen then
|
||||||
|
--b is just circle
|
||||||
|
return intersect.line_circle_collide(a_start, a_end, a_rad, b_start, b_rad, into)
|
||||||
|
end
|
||||||
|
--otherwise we're _actually_ 2 line segs :)
|
||||||
|
if into == nil then into = vec2:zero() end
|
||||||
|
|
||||||
|
--first, check intersection
|
||||||
|
|
||||||
|
--(c to lua translation of paul bourke's
|
||||||
|
-- line intersection algorithm)
|
||||||
|
local dx1 = (a_end.x - a_start.x)
|
||||||
|
local dx2 = (b_end.x - b_start.x)
|
||||||
|
local dy1 = (a_end.y - a_start.y)
|
||||||
|
local dy2 = (b_end.y - b_start.y)
|
||||||
|
local dxab = (a_start.x - b_start.x)
|
||||||
|
local dyab = (a_start.y - b_start.y)
|
||||||
|
|
||||||
|
local denom = dy2 * dx1 - dx2 * dy1
|
||||||
|
local numera = dx2 * dyab - dy2 * dxab
|
||||||
|
local numerb = dx1 * dyab - dy1 * dxab
|
||||||
|
|
||||||
|
--check coincident lines
|
||||||
|
local intersected = "none"
|
||||||
|
if
|
||||||
|
math.abs(numera) < COLLIDE_EPS and
|
||||||
|
math.abs(numerb) < COLLIDE_EPS and
|
||||||
|
math.abs(denom) < COLLIDE_EPS
|
||||||
|
then
|
||||||
|
intersected = "both"
|
||||||
|
else
|
||||||
|
--check parallel, non-coincident lines
|
||||||
|
if math.abs(denom) < COLLIDE_EPS then
|
||||||
|
intersected = "none"
|
||||||
|
else
|
||||||
|
--get interpolants along segments
|
||||||
|
local mua = numera / denom
|
||||||
|
local mub = numerb / denom
|
||||||
|
--intersection outside segment bounds?
|
||||||
|
local outside_a = mua < 0 or mua > 1
|
||||||
|
local outside_b = mub < 0 or mub > 1
|
||||||
|
if outside_a and outside_b then
|
||||||
|
intersected = "none"
|
||||||
|
elseif outside_a then
|
||||||
|
intersected = "b"
|
||||||
|
elseif outside_b then
|
||||||
|
intersected = "a"
|
||||||
|
else
|
||||||
|
intersected = "both"
|
||||||
|
--collision point =
|
||||||
|
--[[vec2:xy(
|
||||||
|
a_start.x + mua * dx1,
|
||||||
|
a_start.y + mua * dy1,
|
||||||
|
)]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if intersected == "both" then
|
||||||
|
--simply displace along A normal
|
||||||
|
return into:vset(a_dir):normalisei():smuli(a_rad + b_rad):rot90li()
|
||||||
|
else
|
||||||
|
--dumb as a rock check-corners approach
|
||||||
|
--todo: pool storage
|
||||||
|
--todo calculus from http://geomalgorithms.com/a07-_distance.html
|
||||||
|
local search_tab = {}
|
||||||
|
--only insert corners from the non-intersected line
|
||||||
|
--since intersected line is potentially the apex
|
||||||
|
if intersected ~= "a" then
|
||||||
|
--a endpoints
|
||||||
|
table.insert(search_tab, {intersect._line_to_point(b_start, b_end, a_start), 1})
|
||||||
|
table.insert(search_tab, {intersect._line_to_point(b_start, b_end, a_end), 1})
|
||||||
|
end
|
||||||
|
if intersected ~= "b" then
|
||||||
|
--b endpoints
|
||||||
|
table.insert(search_tab, {intersect._line_to_point(a_start, a_end, b_start), -1})
|
||||||
|
table.insert(search_tab, {intersect._line_to_point(a_start, a_end, b_end), -1})
|
||||||
|
end
|
||||||
|
|
||||||
|
local best = table.find_best(search_tab, function(v)
|
||||||
|
return -(v[1]:length_squared())
|
||||||
|
end)
|
||||||
|
|
||||||
|
--fix direction
|
||||||
|
into:vset(best[1]):smuli(best[2])
|
||||||
|
|
||||||
|
return intersect._line_displacement_to_sep(a_start, a_end, into, a_rad + b_rad)
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
-- axis aligned bounding boxes
|
||||||
|
|
||||||
|
--return true on overlap, false otherwise
|
||||||
|
local _apo_delta = vec2:zero()
|
||||||
|
function intersect.aabb_point_overlap(pos, hs, v)
|
||||||
|
_apo_delta:vset(pos):vsubi(v):absi()
|
||||||
|
return _apo_delta.x < hs.x and _apo_delta.y < hs.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--return true on overlap, false otherwise
|
||||||
|
local _aao_abs_delta = vec2:zero()
|
||||||
|
local _aao_total_size = vec2:zero()
|
||||||
|
function intersect.aabb_aabb_overlap(pos, hs, opos, ohs)
|
||||||
|
_aao_abs_delta:vset(pos):vsubi(opos):absi()
|
||||||
|
_aao_total_size:vset(hs):vaddi(ohs)
|
||||||
|
return _aao_abs_delta.x < _aao_total_size.x and _aao_abs_delta.y < _aao_total_size.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--discrete displacement
|
||||||
|
--return msv on collision, false otherwise
|
||||||
|
local _aac_delta = vec2:zero()
|
||||||
|
local _aac_abs_delta = vec2:zero()
|
||||||
|
local _aac_size = vec2:zero()
|
||||||
|
local _aac_abs_amount = vec2:zero()
|
||||||
|
function intersect.aabb_aabb_collide(apos, ahs, bpos, bhs, into)
|
||||||
|
if not into then into = vec2:zero() end
|
||||||
|
_aac_delta:vset(apos):vsubi(bpos)
|
||||||
|
_aac_abs_delta:vset(_aac_delta):absi()
|
||||||
|
_aac_size:vset(ahs):vaddi(bhs)
|
||||||
|
_aac_abs_amount:vset(_aac_size):vsubi(_aac_abs_delta)
|
||||||
|
if _aac_abs_amount.x > COLLIDE_EPS and _aac_abs_amount.y > COLLIDE_EPS then
|
||||||
|
--actually collided
|
||||||
|
if _aac_abs_amount.x <= _aac_abs_amount.y then
|
||||||
|
--x min
|
||||||
|
if _aac_delta.x < 0 then
|
||||||
|
return into:sset(-_aac_abs_amount.x, 0)
|
||||||
|
else
|
||||||
|
return into:sset(_aac_abs_amount.x, 0)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
--y min
|
||||||
|
if _aac_delta.y < 0 then
|
||||||
|
return into:sset(0, -_aac_abs_amount.y)
|
||||||
|
else
|
||||||
|
return into:sset(0, _aac_abs_amount.y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--return normal and fraction of dt encountered on collision, false otherwise
|
||||||
|
--TODO: re-pool storage here
|
||||||
|
function intersect.aabb_aabb_collide_continuous(
|
||||||
|
a_startpos, a_endpos, ahs,
|
||||||
|
b_startpos, b_endpos, bhs,
|
||||||
|
into
|
||||||
|
)
|
||||||
|
if not into then into = vec2:zero() end
|
||||||
|
|
||||||
|
--compute delta motion
|
||||||
|
local _self_delta_motion = a_endpos:vsub(a_startpos)
|
||||||
|
local _other_delta_motion = b_endpos:vsub(b_startpos)
|
||||||
|
|
||||||
|
--cheap "is this even possible" early-out
|
||||||
|
do
|
||||||
|
local _self_half_delta = _self_delta_motion:smul(0.5)
|
||||||
|
local _self_bounds_pos = _self_half_delta:vadd(a_endpos)
|
||||||
|
local _self_bounds_hs = _self_half_delta:vadd(ahs)
|
||||||
|
|
||||||
|
local _other_half_delta = _other_delta_motion:smul(0.5)
|
||||||
|
local _other_bounds_pos = _other_half_delta:vadd(b_endpos)
|
||||||
|
local _other_bounds_hs = _other_half_delta:vadd(bhs)
|
||||||
|
|
||||||
|
if not body._overlap_raw(
|
||||||
|
_self_bounds_pos, _self_bounds_hs,
|
||||||
|
_other_bounds_pos, _other_bounds_hs
|
||||||
|
) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--get ccd minkowski box
|
||||||
|
--this is a relative-space box
|
||||||
|
local _relative_delta_motion = _self_delta_motion:vsub(_other_delta_motion)
|
||||||
|
local _minkowski_halfsize = ahs:vadd(bhs)
|
||||||
|
local _minkowski_pos = b_startpos:vsub(a_startpos)
|
||||||
|
|
||||||
|
--if a line seg from our relative motion hits the minkowski box, we're in luck
|
||||||
|
--slab raycast is speedy
|
||||||
|
|
||||||
|
--alias
|
||||||
|
local _rmx = _relative_delta_motion.x
|
||||||
|
local _rmy = _relative_delta_motion.y
|
||||||
|
|
||||||
|
local _inv_x = math.huge
|
||||||
|
if _rmx ~= 0 then _inv_x = 1 / _rmx end
|
||||||
|
local _inv_y = math.huge
|
||||||
|
if _rmy ~= 0 then _inv_y = 1 / _rmy end
|
||||||
|
|
||||||
|
local _minkowski_tl = _minkowski_pos:vsub(_minkowski_halfsize)
|
||||||
|
local _minkowski_br = _minkowski_pos:vadd(_minkowski_halfsize)
|
||||||
|
|
||||||
|
--clip x
|
||||||
|
--get edge t along line
|
||||||
|
local tx1 = (_minkowski_tl.x) * _inv_x
|
||||||
|
local tx2 = (_minkowski_br.x) * _inv_x
|
||||||
|
--clip to existing clip space
|
||||||
|
local txmin = math.min(tx1, tx2)
|
||||||
|
local txmax = math.max(tx1, tx2)
|
||||||
|
--clip y
|
||||||
|
--get edge t along line
|
||||||
|
local ty1 = (_minkowski_tl.y) * _inv_y
|
||||||
|
local ty2 = (_minkowski_br.y) * _inv_y
|
||||||
|
--clip to existing clip space
|
||||||
|
local tymin = math.min(ty1, ty2)
|
||||||
|
local tymax = math.max(ty1, ty2)
|
||||||
|
|
||||||
|
--clip space
|
||||||
|
local tmin = math.max(0, txmin, tymin)
|
||||||
|
local tmax = math.min(1, txmax, tymax)
|
||||||
|
|
||||||
|
--still some unclipped? collision!
|
||||||
|
if tmin <= tmax then
|
||||||
|
--"was colliding at start"
|
||||||
|
if tmin == 0 then
|
||||||
|
--todo: maybe collide at old pos, not new pos
|
||||||
|
local msv = self:collide(other, into)
|
||||||
|
if msv then
|
||||||
|
return msv, tmin
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--delta before colliding
|
||||||
|
local _self_collide_pre = _self_delta_motion:smul(tmin)
|
||||||
|
--delta after colliding (to be discarded or projected or whatever)
|
||||||
|
local _self_collide_post = _self_delta_motion:smul(1.0 - tmin)
|
||||||
|
--get the collision normal
|
||||||
|
--(whichever boundary crossed _last_ -> normal)
|
||||||
|
local _self_collide_normal = vec2:zero()
|
||||||
|
if txmin > tymin then
|
||||||
|
_self_collide_normal.x = -math.sign(_self_delta_motion.x)
|
||||||
|
else
|
||||||
|
_self_collide_normal.y = -math.sign(_self_delta_motion.y)
|
||||||
|
end
|
||||||
|
|
||||||
|
--travelling away from normal?
|
||||||
|
if _self_collide_normal:dot(_self_delta_motion) >= 0 then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--just "slide" projection for now
|
||||||
|
_self_collide_post:vreji(_self_collide_normal)
|
||||||
|
|
||||||
|
--combine
|
||||||
|
local _final_delta = _self_collide_pre:vadd(_self_collide_post)
|
||||||
|
|
||||||
|
--construct the target position
|
||||||
|
local _target_pos = a_startpos:vadd(_final_delta)
|
||||||
|
|
||||||
|
--return delta to target pos
|
||||||
|
local msv = _target_pos:vsub(a_endpos)
|
||||||
|
|
||||||
|
if math.abs(msv.x) > COLLIDE_EPS or math.abs(msv.y) > COLLIDE_EPS then
|
||||||
|
into:vset(msv)
|
||||||
|
return into, tmin
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return intersect
|
212
math.lua
Normal file
212
math.lua
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
--[[
|
||||||
|
extra mathematical functions
|
||||||
|
]]
|
||||||
|
|
||||||
|
local bit = require("bit")
|
||||||
|
|
||||||
|
--wrap v around range [lo, hi)
|
||||||
|
function math.wrap(v, lo, hi)
|
||||||
|
local range = hi - lo
|
||||||
|
local relative = v - lo
|
||||||
|
local relative_wrapped = relative % range
|
||||||
|
local relative_add = relative_wrapped + range
|
||||||
|
local final_wrap = relative_add % range
|
||||||
|
return lo + final_wrap
|
||||||
|
end
|
||||||
|
|
||||||
|
--clamp v to range [lo, hi]
|
||||||
|
function math.clamp(v, lo, hi)
|
||||||
|
return math.max(lo, math.min(v, hi))
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.clamp01(v)
|
||||||
|
return math.clamp(v, 0, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--round v to nearest whole
|
||||||
|
function math.round(v)
|
||||||
|
return math.floor(v + 0.5)
|
||||||
|
end
|
||||||
|
|
||||||
|
--round v to one-in x
|
||||||
|
-- (eg x = 2, v rounded to increments of 0.5)
|
||||||
|
function math.to_one_in(v, x)
|
||||||
|
return math.round(v * x) / x
|
||||||
|
end
|
||||||
|
|
||||||
|
--round v to a given decimal precision
|
||||||
|
function math.to_precision(v, decimal_points)
|
||||||
|
return math.to_one_in(v, math.pow(10, decimal_points))
|
||||||
|
end
|
||||||
|
|
||||||
|
--0, 1, -1 sign of a scalar
|
||||||
|
function math.sign(v)
|
||||||
|
if v < 0 then return -1 end
|
||||||
|
if v > 0 then return 1 end
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
--linear interpolation between a and b
|
||||||
|
function math.lerp(a, b, t)
|
||||||
|
return a * (1.0 - t) + b * t
|
||||||
|
end
|
||||||
|
|
||||||
|
--classic smoothstep
|
||||||
|
--(only "safe" for 0-1 range)
|
||||||
|
function math.smoothstep(v)
|
||||||
|
return v * v * (3 - 2 * v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--classic smootherstep; zero 2nd order derivatives at 0 and 1
|
||||||
|
--(only safe for 0-1 range)
|
||||||
|
function math.smootherstep(v)
|
||||||
|
return v * v * v * (v * (v * 6 - 15) + 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
--todo: various other easing curves
|
||||||
|
|
||||||
|
--prime number stuff
|
||||||
|
local primes_1k = {
|
||||||
|
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
|
||||||
|
73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173,
|
||||||
|
179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281,
|
||||||
|
283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,
|
||||||
|
419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541,
|
||||||
|
547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659,
|
||||||
|
661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
|
||||||
|
811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941,
|
||||||
|
947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069,
|
||||||
|
1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223,
|
||||||
|
1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373,
|
||||||
|
1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511,
|
||||||
|
1523, 1531, 1543, 1549, 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, 1657,
|
||||||
|
1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, 1777, 1783, 1787, 1789, 1801, 1811,
|
||||||
|
1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987,
|
||||||
|
1993, 1997, 1999, 2003, 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, 2129,
|
||||||
|
2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, 2251, 2267, 2269, 2273, 2281, 2287,
|
||||||
|
2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423,
|
||||||
|
2437, 2441, 2447, 2459, 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, 2617,
|
||||||
|
2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, 2711, 2713, 2719, 2729, 2731, 2741,
|
||||||
|
2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903,
|
||||||
|
2909, 2917, 2927, 2939, 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, 3079,
|
||||||
|
3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, 3217, 3221, 3229, 3251, 3253, 3257,
|
||||||
|
3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413,
|
||||||
|
3433, 3449, 3457, 3461, 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, 3571,
|
||||||
|
3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727,
|
||||||
|
3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907,
|
||||||
|
3911, 3917, 3919, 3923, 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, 4057,
|
||||||
|
4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, 4201, 4211, 4217, 4219, 4229, 4231,
|
||||||
|
4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409,
|
||||||
|
4421, 4423, 4441, 4447, 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, 4583,
|
||||||
|
4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, 4703, 4721, 4723, 4729, 4733, 4751,
|
||||||
|
4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937,
|
||||||
|
4943, 4951, 4957, 4967, 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, 5087,
|
||||||
|
5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, 5231, 5233, 5237, 5261, 5273, 5279,
|
||||||
|
5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443,
|
||||||
|
5449, 5471, 5477, 5479, 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, 5639,
|
||||||
|
5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, 5741, 5743, 5749, 5779, 5783, 5791,
|
||||||
|
5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939,
|
||||||
|
5953, 5981, 5987, 6007, 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, 6133,
|
||||||
|
6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, 6269, 6271, 6277, 6287, 6299, 6301,
|
||||||
|
6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473,
|
||||||
|
6481, 6491, 6521, 6529, 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, 6673,
|
||||||
|
6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, 6793, 6803, 6823, 6827, 6829, 6833,
|
||||||
|
6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997,
|
||||||
|
7001, 7013, 7019, 7027, 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, 7207,
|
||||||
|
7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, 7333, 7349, 7351, 7369, 7393, 7411,
|
||||||
|
7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561,
|
||||||
|
7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723,
|
||||||
|
7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919,
|
||||||
|
}
|
||||||
|
|
||||||
|
local sparse_primes_1k = {
|
||||||
|
2, 3, 5, 11, 19, 37, 59, 79, 109, 151, 191, 239, 293, 367, 431, 499, 571, 653, 733, 853, 1009, 1151, 1301, 1481, 1613,
|
||||||
|
1783, 1951, 2113, 2137, 2311, 2477, 2687, 2857, 3079, 3323, 3541, 3853, 4211, 4549, 4933, 5101, 5479, 5843, 6247, 6653,
|
||||||
|
6689, 7039, 7307, 7559, 7573, 7919,
|
||||||
|
}
|
||||||
|
|
||||||
|
function math.first_above(v, t)
|
||||||
|
for _,p in ipairs(t) do
|
||||||
|
if p > v then
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return t[#t]
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.next_prime_1k(v)
|
||||||
|
return math.first_above(v, primes_1k)
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.next_prime_1k_sparse(v)
|
||||||
|
return math.first_above(v, sparse_primes_1k)
|
||||||
|
end
|
||||||
|
|
||||||
|
--color handling stuff
|
||||||
|
local band, bor = bit.band, bit.bor
|
||||||
|
local lshift, rshift = bit.lshift, bit.rshift
|
||||||
|
|
||||||
|
function math.colorToRGB(r, g, b)
|
||||||
|
local br = lshift(band(0xff, r * 255), 16)
|
||||||
|
local bg = lshift(band(0xff, g * 255), 8)
|
||||||
|
local bb = lshift(band(0xff, b * 255), 0)
|
||||||
|
return bor( br, bg, bb )
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.colorToARGB(r, g, b, a)
|
||||||
|
local ba = lshift(band(0xff, a * 255), 24)
|
||||||
|
local br = lshift(band(0xff, r * 255), 16)
|
||||||
|
local bg = lshift(band(0xff, g * 255), 8)
|
||||||
|
local bb = lshift(band(0xff, b * 255), 0)
|
||||||
|
return bor( br, bg, bb, ba )
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.colorToRGBA(r, g, b, a)
|
||||||
|
local br = lshift(band(0xff, r * 255), 24)
|
||||||
|
local bg = lshift(band(0xff, g * 255), 16)
|
||||||
|
local bb = lshift(band(0xff, b * 255), 8)
|
||||||
|
local ba = lshift(band(0xff, a * 255), 0)
|
||||||
|
return bor( br, bg, bb, ba )
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.ARGBToColor(argb)
|
||||||
|
local r = rshift(band(argb, 0x00ff0000), 16) / 255.0
|
||||||
|
local g = rshift(band(argb, 0x0000ff00), 8) / 255.0
|
||||||
|
local b = rshift(band(argb, 0x000000ff), 0) / 255.0
|
||||||
|
local a = rshift(band(argb, 0xff000000), 24) / 255.0
|
||||||
|
return r, g, b, a
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.RGBAToColor(rgba)
|
||||||
|
local r = rshift(band(rgba, 0xff000000), 24) / 255.0
|
||||||
|
local g = rshift(band(rgba, 0x00ff0000), 16) / 255.0
|
||||||
|
local b = rshift(band(rgba, 0x0000ff00), 8) / 255.0
|
||||||
|
local a = rshift(band(rgba, 0x000000ff), 0) / 255.0
|
||||||
|
return r, g, b, a
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.RGBToColor(rgb)
|
||||||
|
local r = rshift(band(rgb, 0x00ff0000), 16) / 255.0
|
||||||
|
local g = rshift(band(rgb, 0x0000ff00), 8) / 255.0
|
||||||
|
local b = rshift(band(rgb, 0x000000ff), 0) / 255.0
|
||||||
|
local a = 1.0
|
||||||
|
return r, g, b, a
|
||||||
|
end
|
||||||
|
|
||||||
|
--angle handling stuff
|
||||||
|
function math.normalise_angle(a)
|
||||||
|
return math.wrap(a, -math.pi, math.pi)
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.relative_angle(a1, a2)
|
||||||
|
a1 = math.normalise_angle(a1)
|
||||||
|
a2 = math.normalise_angle(a2)
|
||||||
|
return math.normalise_angle(a1 - a2)
|
||||||
|
end
|
||||||
|
|
||||||
|
--geometric rotation multi-return
|
||||||
|
function math.rotate(x, y, r)
|
||||||
|
local s = math.sin(r)
|
||||||
|
local c = math.cos(r)
|
||||||
|
return c * x - s * y, s * x + c * y
|
||||||
|
end
|
43
oo.lua
Normal file
43
oo.lua
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
--[[
|
||||||
|
barebones oop basics
|
||||||
|
supports basic inheritance and means you don't have to build/set your own metatable each time
|
||||||
|
|
||||||
|
todo: collect some stats on classes/optional global class registry
|
||||||
|
]]
|
||||||
|
|
||||||
|
function class(inherits)
|
||||||
|
local c = {}
|
||||||
|
c.__mt = {__index = c}
|
||||||
|
--handle single inheritence
|
||||||
|
if type(inherits) == "table" and inherits.__mt then
|
||||||
|
setmetatable(c, inherits.__mt)
|
||||||
|
end
|
||||||
|
--common class functions
|
||||||
|
|
||||||
|
--internal initialisation
|
||||||
|
--sets up an initialised object with a default value table
|
||||||
|
--performing a super construction if necessary and assigning the right metatable
|
||||||
|
function c:init(t, ...)
|
||||||
|
if inherits then
|
||||||
|
--super ctor, then overlay args table
|
||||||
|
t = table.overlay(inherits:new(...), t)
|
||||||
|
end
|
||||||
|
--upgrade to this class and return
|
||||||
|
return setmetatable(t, self.__mt)
|
||||||
|
end
|
||||||
|
|
||||||
|
--constructor
|
||||||
|
--generally to be overridden
|
||||||
|
function c:new()
|
||||||
|
return self:init({})
|
||||||
|
end
|
||||||
|
|
||||||
|
--get the inherited class for super calls if/as needed
|
||||||
|
--allows overrides that still refer to superclass behaviour
|
||||||
|
function c:super()
|
||||||
|
return inherits
|
||||||
|
end
|
||||||
|
|
||||||
|
--done
|
||||||
|
return c
|
||||||
|
end
|
152
stable_sort.lua
Normal file
152
stable_sort.lua
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
-- stable sorting routines for lua
|
||||||
|
--
|
||||||
|
-- modifies the global table namespace so you don't have
|
||||||
|
-- to re-require it everywhere.
|
||||||
|
--
|
||||||
|
-- table.stable_sort
|
||||||
|
-- a fast stable sort
|
||||||
|
-- table.unstable_sort
|
||||||
|
-- alias for the builtin unstable table.sort
|
||||||
|
-- table.insertion_sort
|
||||||
|
-- an insertion sort, should you prefer it
|
||||||
|
--
|
||||||
|
|
||||||
|
--this is based on MIT licensed code from Dirk Laurie and Steve Fisher
|
||||||
|
--license as follows:
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Copyright © 2013 Dirk Laurie and Steve Fisher.
|
||||||
|
|
||||||
|
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.
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- (modifications by Max Cahill 2018)
|
||||||
|
|
||||||
|
local _sort_core = {}
|
||||||
|
|
||||||
|
--tunable size for
|
||||||
|
_sort_core.max_chunk_size = 24
|
||||||
|
|
||||||
|
function _sort_core.insertion_sort_impl( array, first, last, less )
|
||||||
|
for i = first + 1, last do
|
||||||
|
local k = first
|
||||||
|
local v = array[i]
|
||||||
|
for j = i, first + 1, -1 do
|
||||||
|
if less( v, array[j-1] ) then
|
||||||
|
array[j] = array[j-1]
|
||||||
|
else
|
||||||
|
k = j
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
array[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function _sort_core.merge( array, workspace, low, middle, high, less )
|
||||||
|
local i, j, k
|
||||||
|
i = 1
|
||||||
|
-- copy first half of array to auxiliary array
|
||||||
|
for j = low, middle do
|
||||||
|
workspace[ i ] = array[ j ]
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
-- sieve through
|
||||||
|
i = 1
|
||||||
|
j = middle + 1
|
||||||
|
k = low
|
||||||
|
while true do
|
||||||
|
if (k >= j) or (j > high) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
if less( array[ j ], workspace[ i ] ) then
|
||||||
|
array[ k ] = array[ j ]
|
||||||
|
j = j + 1
|
||||||
|
else
|
||||||
|
array[ k ] = workspace[ i ]
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
k = k + 1
|
||||||
|
end
|
||||||
|
-- copy back any remaining elements of first half
|
||||||
|
for k = k, j-1 do
|
||||||
|
array[ k ] = workspace[ i ]
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function _sort_core.merge_sort_impl(array, workspace, low, high, less)
|
||||||
|
if high - low <= _sort_core.max_chunk_size then
|
||||||
|
_sort_core.insertion_sort_impl( array, low, high, less )
|
||||||
|
else
|
||||||
|
local middle = math.floor((low + high)/2)
|
||||||
|
_sort_core.merge_sort_impl( array, workspace, low, middle, less )
|
||||||
|
_sort_core.merge_sort_impl( array, workspace, middle + 1, high, less )
|
||||||
|
_sort_core.merge( array, workspace, low, middle, high, less )
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--inline common setup stuff
|
||||||
|
function _sort_core.sort_setup(array, less)
|
||||||
|
local n = #array
|
||||||
|
local trivial = false
|
||||||
|
--trivial cases; empty or 1 element
|
||||||
|
if n <= 1 then
|
||||||
|
trivial = true
|
||||||
|
else
|
||||||
|
--default less
|
||||||
|
less = less or function (a, b)
|
||||||
|
return a < b
|
||||||
|
end
|
||||||
|
--check less
|
||||||
|
if less(array[1], array[1]) then
|
||||||
|
error("invalid order function for sorting")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
--setup complete
|
||||||
|
return trivial, n, less
|
||||||
|
end
|
||||||
|
|
||||||
|
function _sort_core.stable_sort(array, less)
|
||||||
|
--setup
|
||||||
|
local trivial, n, less = _sort_core.sort_setup(array, less)
|
||||||
|
if not trivial then
|
||||||
|
--temp storage
|
||||||
|
local workspace = {}
|
||||||
|
workspace[ math.floor( (n+1)/2 ) ] = array[1]
|
||||||
|
--dive in
|
||||||
|
_sort_core.merge_sort_impl( array, workspace, 1, n, less )
|
||||||
|
end
|
||||||
|
return array
|
||||||
|
end
|
||||||
|
|
||||||
|
function _sort_core.insertion_sort(array, less)
|
||||||
|
--setup
|
||||||
|
local trivial, n, less = _sort_core.sort_setup(array, less)
|
||||||
|
if not trivial then
|
||||||
|
_sort_core.insertion_sort_impl(array, 1, n, less)
|
||||||
|
end
|
||||||
|
return array
|
||||||
|
end
|
||||||
|
|
||||||
|
--export sort core
|
||||||
|
table.insertion_sort = _sort_core.insertion_sort
|
||||||
|
table.stable_sort = _sort_core.stable_sort
|
||||||
|
table.unstable_sort = table.sort
|
135
state_machine.lua
Normal file
135
state_machine.lua
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
--[[
|
||||||
|
state machine
|
||||||
|
|
||||||
|
a finite state machine implementation;
|
||||||
|
each state is a table with optional enter, exit, update and draw callbacks
|
||||||
|
which each optionally take the machine, and the state table as arguments
|
||||||
|
|
||||||
|
on changing state, the outgoing state's exit callback is called, then the incoming state's
|
||||||
|
enter callback is called.
|
||||||
|
|
||||||
|
on update, the current state's update callback is called
|
||||||
|
on draw, the current state's draw callback is called
|
||||||
|
|
||||||
|
TODO: consider coroutine friendliness
|
||||||
|
|
||||||
|
depends on oo.lua supplying class()
|
||||||
|
]]
|
||||||
|
|
||||||
|
local state_machine = class()
|
||||||
|
|
||||||
|
function state_machine:new(states, start)
|
||||||
|
local ret = self:init({
|
||||||
|
states = states or {},
|
||||||
|
current_state = ""
|
||||||
|
})
|
||||||
|
|
||||||
|
if start then
|
||||||
|
ret:set_state(start)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
--internal helpers
|
||||||
|
|
||||||
|
function state_machine:_get_state()
|
||||||
|
return self.states[self.current_state]
|
||||||
|
end
|
||||||
|
|
||||||
|
--make an internal call, with up to 4 arguments
|
||||||
|
function state_machine:_call(name, a, b, c, d)
|
||||||
|
local state = self:_get_state()
|
||||||
|
if state and type(state[name]) == "function" then
|
||||||
|
return state[name](self, state, a, b, c, d)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
--various checks
|
||||||
|
|
||||||
|
function state_machine:in_state(name)
|
||||||
|
return self.current_state == name
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_machine:has_state(name)
|
||||||
|
return self.states[name] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
--state adding/removing
|
||||||
|
|
||||||
|
--add a state
|
||||||
|
function state_machine:add_state(name, data)
|
||||||
|
if self.has_state(name) then
|
||||||
|
error("error: added duplicate state "..name)
|
||||||
|
else
|
||||||
|
self.states[name] = data
|
||||||
|
if self:in_state(name) then
|
||||||
|
self:_call("enter")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--remove a state
|
||||||
|
function state_machine:remove_state(name)
|
||||||
|
if not self.has_state(name) then
|
||||||
|
error("error: removed missed state "..name)
|
||||||
|
else
|
||||||
|
if self:in_state(name) then
|
||||||
|
self:_call("exit")
|
||||||
|
end
|
||||||
|
self.states[name] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--hard-replace a state table
|
||||||
|
--if do_transitions is truthy and we're replacing the current state,
|
||||||
|
--exit is called on the old state and enter is called on the new state
|
||||||
|
function state_machine:replace_state(name, data, do_transitions)
|
||||||
|
local current = self:in_state(name)
|
||||||
|
if do_transitions and current then
|
||||||
|
self:_call("exit")
|
||||||
|
end
|
||||||
|
self.states[name] = data
|
||||||
|
if do_transitions and current then
|
||||||
|
self:_call("enter")
|
||||||
|
end
|
||||||
|
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--ensure a state doesn't exist
|
||||||
|
function state_machine:clear_state(name)
|
||||||
|
return self:replace_state(name, nil, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
--transitions and updates
|
||||||
|
|
||||||
|
function state_machine:set_state(state, reset)
|
||||||
|
if self.current_state ~= state or reset then
|
||||||
|
self:_call("exit")
|
||||||
|
self.current_state = state
|
||||||
|
self:_call("enter")
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--perform an update
|
||||||
|
--pass in an optional delta time which is passed as an arg to the state functions
|
||||||
|
function state_machine:update(dt)
|
||||||
|
return self:_call("update", dt)
|
||||||
|
end
|
||||||
|
|
||||||
|
function state_machine:draw()
|
||||||
|
self:_call("draw")
|
||||||
|
end
|
||||||
|
|
||||||
|
return state_machine
|
108
table.lua
Normal file
108
table.lua
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
--[[
|
||||||
|
extra table routines
|
||||||
|
]]
|
||||||
|
|
||||||
|
--return the back element of a table
|
||||||
|
function table.back(t)
|
||||||
|
return t[#t]
|
||||||
|
end
|
||||||
|
|
||||||
|
--remove the back element of a table and return it
|
||||||
|
function table.pop(t)
|
||||||
|
return table.remove(t)
|
||||||
|
end
|
||||||
|
|
||||||
|
--insert to the back of a table
|
||||||
|
function table.push(t, v)
|
||||||
|
return table.insert(t, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--remove the front element of a table and return it
|
||||||
|
function table.shift(t)
|
||||||
|
return table.remove(t, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
--insert to the front of a table
|
||||||
|
function table.unshift(t, v)
|
||||||
|
return table.insert(t, 1, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
--find the index in a sequential table that a resides at
|
||||||
|
--or nil if nothing was found
|
||||||
|
--(todo: consider pairs version?)
|
||||||
|
function table.index_of(t, a)
|
||||||
|
if a == nil then return nil end
|
||||||
|
for i,b in ipairs(t) do
|
||||||
|
if a == b then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--remove the first instance of value from a table (linear search)
|
||||||
|
--returns true if the value was removed, else false
|
||||||
|
function table.remove_value(t, a)
|
||||||
|
local i = table.index_of(t, a)
|
||||||
|
if i then
|
||||||
|
table.remove(t, i)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--add a value to a table if it doesn't already exist (linear search)
|
||||||
|
--returns true if the value was added, else false
|
||||||
|
function table.add_value(t, a)
|
||||||
|
local i = table.index_of(t, a)
|
||||||
|
if not i then
|
||||||
|
table.insert(t, a)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--helper for optionally passed random
|
||||||
|
local _global_random = love.math.random or math.random
|
||||||
|
local function _random(min, max, r)
|
||||||
|
return r and r:random(min, max)
|
||||||
|
or _global_random(min, max)
|
||||||
|
end
|
||||||
|
|
||||||
|
--pick a random value from a table (or nil if it's empty)
|
||||||
|
function table.pick_random(t, r)
|
||||||
|
if #t == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return t[_random(1, #t, r)]
|
||||||
|
end
|
||||||
|
|
||||||
|
--shuffle the order of a table
|
||||||
|
function table.shuffle(t, r)
|
||||||
|
for i = 1, #t do
|
||||||
|
local j = _random(1, #t, r)
|
||||||
|
t[i], t[j] = t[j], t[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--(might already exist depending on luajit)
|
||||||
|
if table.clear == nil then
|
||||||
|
--destructively clear a numerically keyed table
|
||||||
|
--useful when multiple references are floating around
|
||||||
|
--so you cannot just pop a new table out of nowhere
|
||||||
|
function table.clear(t)
|
||||||
|
assert(type(to) == "table", "table.overlay - argument 'to' must be a table")
|
||||||
|
while t[1] ~= nil do
|
||||||
|
table.remove(t)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.overlay(to, from)
|
||||||
|
assert(type(to) == "table", "table.overlay - argument 'to' must be a table")
|
||||||
|
assert(type(from) == "table", "table.overlay - argument 'from' must be a table")
|
||||||
|
for k,v in pairs(from) do
|
||||||
|
to[k] = v
|
||||||
|
end
|
||||||
|
return to
|
||||||
|
end
|
579
vec2.lua
Normal file
579
vec2.lua
Normal file
@ -0,0 +1,579 @@
|
|||||||
|
--[[
|
||||||
|
2d vector type
|
||||||
|
]]
|
||||||
|
|
||||||
|
--[[
|
||||||
|
notes:
|
||||||
|
|
||||||
|
depends on a class() function as in oo.lua
|
||||||
|
|
||||||
|
some methods depend on math library extensions
|
||||||
|
|
||||||
|
math.clamp(v, min, max) - return v clamped between min and max
|
||||||
|
math.round(v) - round v downwards if fractional part is < 0.5
|
||||||
|
]]
|
||||||
|
|
||||||
|
local vec2 = class()
|
||||||
|
vec2.x = 0
|
||||||
|
vec2.y = 0
|
||||||
|
|
||||||
|
--probably-too-flexible ctor
|
||||||
|
function vec2:new(x, y)
|
||||||
|
if x and y then
|
||||||
|
return vec2:xy(x,y)
|
||||||
|
elseif x then
|
||||||
|
if type(x) == "number" then
|
||||||
|
return vec2:filled(x)
|
||||||
|
elseif x.copy then
|
||||||
|
return x:copy()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return vec2:zero()
|
||||||
|
end
|
||||||
|
|
||||||
|
--explicit ctors
|
||||||
|
function vec2:copy()
|
||||||
|
return self:init({
|
||||||
|
x = self.x, y = self.y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:xy(x, y)
|
||||||
|
return self:init({
|
||||||
|
x = x, y = y
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:filled(v)
|
||||||
|
return self:init({
|
||||||
|
x = v, y = v
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:zero()
|
||||||
|
return vec2:filled(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
--shared pooled storage
|
||||||
|
local _vec2_pool = {}
|
||||||
|
--size limit for tuning memory upper bound
|
||||||
|
local _vec2_pool_limit = 128
|
||||||
|
|
||||||
|
function vec2.pool_size()
|
||||||
|
return #_vec2_pool
|
||||||
|
end
|
||||||
|
|
||||||
|
--flush the entire pool
|
||||||
|
function vec2.flush_pool()
|
||||||
|
if vec2.pool_size() > 0 then
|
||||||
|
_vec2_pool = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--drain one element from the pool, if it exists
|
||||||
|
function vec2.drain_pool()
|
||||||
|
if #_vec2_pool > 0 then
|
||||||
|
return table.remove(_vec2_pool)
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
--get a pooled vector (initialise it yourself)
|
||||||
|
function vec2:pooled()
|
||||||
|
return vec2.drain_pool() or vec2:zero()
|
||||||
|
end
|
||||||
|
|
||||||
|
--get a pooled copy of an existing vector
|
||||||
|
function vec2:pooled_copy()
|
||||||
|
return vec2:pooled():vset(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
--release a vector to the pool
|
||||||
|
function vec2:release()
|
||||||
|
if vec2.pool_size() < _vec2_pool_limit then
|
||||||
|
table.insert(_vec2_pool, self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--unpack for multi-args
|
||||||
|
|
||||||
|
function vec2:unpack()
|
||||||
|
return self.x, self.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--pack when a sequence is needed
|
||||||
|
--(not particularly useful)
|
||||||
|
|
||||||
|
function vec2:pack()
|
||||||
|
return {self:unpack()}
|
||||||
|
end
|
||||||
|
|
||||||
|
--modify
|
||||||
|
|
||||||
|
function vec2:sset(x, y)
|
||||||
|
if not y then y = x end
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vset(v)
|
||||||
|
self.x = v.x
|
||||||
|
self.y = v.y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:swap(v)
|
||||||
|
local sx, sy = self.x, self.y
|
||||||
|
self:vset(v)
|
||||||
|
v:sset(sx, sy)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
--equality comparison
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
--threshold for equality in each dimension
|
||||||
|
local EQUALS_EPSILON = 1e-9
|
||||||
|
|
||||||
|
--true if a and b are functionally equivalent
|
||||||
|
function vec2.equals(a, b)
|
||||||
|
return (
|
||||||
|
math.abs(a.x - b.x) <= EQUALS_EPSILON and
|
||||||
|
math.abs(a.y - b.y) <= EQUALS_EPSILON
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
--true if a and b are not functionally equivalent
|
||||||
|
--(very slightly faster than `not vec2.equals(a, b)`)
|
||||||
|
function vec2.nequals(a, b)
|
||||||
|
return (
|
||||||
|
math.abs(a.x - b.x) > EQUALS_EPSILON or
|
||||||
|
math.abs(a.y - b.y) > EQUALS_EPSILON
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
--arithmetic
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
--immediate mode
|
||||||
|
|
||||||
|
--vector
|
||||||
|
function vec2:vaddi(v)
|
||||||
|
self.x = self.x + v.x
|
||||||
|
self.y = self.y + v.y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vsubi(v)
|
||||||
|
self.x = self.x - v.x
|
||||||
|
self.y = self.y - v.y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vmuli(v)
|
||||||
|
self.x = self.x * v.x
|
||||||
|
self.y = self.y * v.y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vdivi(v)
|
||||||
|
self.x = self.x / v.x
|
||||||
|
self.y = self.y / v.y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--scalar
|
||||||
|
function vec2:saddi(x, y)
|
||||||
|
if y then
|
||||||
|
self.x = self.x + x
|
||||||
|
self.y = self.y + y
|
||||||
|
else
|
||||||
|
self.x = self.x + x
|
||||||
|
self.y = self.y + x
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:ssubi(x, y)
|
||||||
|
if y then
|
||||||
|
self.x = self.x - x
|
||||||
|
self.y = self.y - y
|
||||||
|
else
|
||||||
|
self.x = self.x - x
|
||||||
|
self.y = self.y - x
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:smuli(x, y)
|
||||||
|
if y then
|
||||||
|
self.x = self.x * x
|
||||||
|
self.y = self.y * y
|
||||||
|
else
|
||||||
|
self.x = self.x * x
|
||||||
|
self.y = self.y * x
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:sdivi(x, y)
|
||||||
|
if y then
|
||||||
|
self.x = self.x / x
|
||||||
|
self.y = self.y / y
|
||||||
|
else
|
||||||
|
self.x = self.x / x
|
||||||
|
self.y = self.y / x
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--garbage mode
|
||||||
|
|
||||||
|
function vec2:vadd(v)
|
||||||
|
return self:copy():vaddi(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vsub(v)
|
||||||
|
return self:copy():vsubi(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vmul(v)
|
||||||
|
return self:copy():vmuli(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:vdiv(v)
|
||||||
|
return self:copy():vdivi(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:sadd(x, y)
|
||||||
|
return self:copy():saddi(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:ssub(x, y)
|
||||||
|
return self:copy():ssubi(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:smul(x, y)
|
||||||
|
return self:copy():smuli(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:sdiv(x, y)
|
||||||
|
return self:copy():sdivi(x, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
--fused multiply-add (a + (b * t))
|
||||||
|
|
||||||
|
function vec2:fmai(v, t)
|
||||||
|
self.x = self.x + (v.x * t)
|
||||||
|
self.y = self.y + (v.y * t)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:fma(v, t)
|
||||||
|
return self:copy():fmai(v, t)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- geometric methods
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
function vec2:length_squared()
|
||||||
|
return self.x * self.x + self.y * self.y
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:length()
|
||||||
|
return math.sqrt(self:length_squared())
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:distance_squared(other)
|
||||||
|
local dx = self.x - other.x
|
||||||
|
local dy = self.y - other.y
|
||||||
|
return dx * dx + dy * dy
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:distance(other)
|
||||||
|
return math.sqrt(self:distance_squared(other))
|
||||||
|
end
|
||||||
|
|
||||||
|
--immediate mode
|
||||||
|
|
||||||
|
function vec2:normalisei_both()
|
||||||
|
local len = self:length()
|
||||||
|
if len == 0 then
|
||||||
|
return self, 0
|
||||||
|
end
|
||||||
|
return self:sdivi(len), len
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:normalisei()
|
||||||
|
local v, len = self:normalisei_both()
|
||||||
|
return v
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:normalisei_len()
|
||||||
|
local v, len = self:normalisei_both()
|
||||||
|
return len
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:inversei()
|
||||||
|
return self:smuli(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rotatei(angle)
|
||||||
|
local s = math.sin(angle)
|
||||||
|
local c = math.cos(angle)
|
||||||
|
local ox = self.x
|
||||||
|
local oy = self.y
|
||||||
|
self.x = c * ox - s * oy
|
||||||
|
self.y = s * ox + c * oy
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rot90ri()
|
||||||
|
local ox = self.x
|
||||||
|
local oy = self.y
|
||||||
|
self.x = -oy
|
||||||
|
self.y = ox
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rot90li()
|
||||||
|
local ox = self.x
|
||||||
|
local oy = self.y
|
||||||
|
self.x = oy
|
||||||
|
self.y = -ox
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
vec2.rot180i = vec2.inversei --alias
|
||||||
|
|
||||||
|
function vec2:rotate_aroundi(angle, pivot)
|
||||||
|
local s = math.sin(angle)
|
||||||
|
local c = math.cos(angle)
|
||||||
|
local ox = self.x - pivot.x
|
||||||
|
local oy = self.y - pivot.y
|
||||||
|
self.x = (c * ox - s * oy) + pivot.x
|
||||||
|
self.y = (s * ox + c * oy) + pivot.y
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rotate_around(angle, pivot)
|
||||||
|
return self:copy():rotate_aroundi(angle, pivot)
|
||||||
|
end
|
||||||
|
|
||||||
|
--garbage mode
|
||||||
|
|
||||||
|
function vec2:normalised()
|
||||||
|
return self:copy():normalisei()
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:normalised_len()
|
||||||
|
local v = self:copy()
|
||||||
|
local len = v:normalisei_len()
|
||||||
|
return v, len
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:inverse()
|
||||||
|
return self:copy():inversei()
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rotate(angle)
|
||||||
|
return self:copy():rotatei(angle)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rot90r()
|
||||||
|
return self:copy():rot90ri()
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:rot90l()
|
||||||
|
return self:copy():rot90li()
|
||||||
|
end
|
||||||
|
|
||||||
|
vec2.rot180 = vec2.inverse --alias
|
||||||
|
|
||||||
|
function vec2:angle()
|
||||||
|
return math.atan2(self.y, self.x)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- per-component clamping ops
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
function vec2:mini(v)
|
||||||
|
self.x = math.min(self.x, v.x)
|
||||||
|
self.y = math.min(self.y, v.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:maxi(v)
|
||||||
|
self.x = math.max(self.x, v.x)
|
||||||
|
self.y = math.max(self.y, v.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:clampi(min, max)
|
||||||
|
self.x = math.clamp(self.x, min.x, max.x)
|
||||||
|
self.y = math.clamp(self.y, min.y, max.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:min(v)
|
||||||
|
return self:copy():mini(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:max(v)
|
||||||
|
return self:copy():maxi(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:clamp(min, max)
|
||||||
|
return self:copy():clampi(min, max)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- absolute value
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
function vec2:absi()
|
||||||
|
self.x = math.abs(self.x)
|
||||||
|
self.y = math.abs(self.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:abs()
|
||||||
|
return self:copy():absi()
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- truncation/rounding
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
function vec2:floori()
|
||||||
|
self.x = math.floor(self.x)
|
||||||
|
self.y = math.floor(self.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:ceili()
|
||||||
|
self.x = math.ceil(self.x)
|
||||||
|
self.y = math.ceil(self.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:roundi()
|
||||||
|
self.x = math.round(self.x)
|
||||||
|
self.y = math.round(self.y)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:floor()
|
||||||
|
return self:copy():floori()
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:ceil()
|
||||||
|
return self:copy():ceili()
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:round()
|
||||||
|
return self:copy():roundi()
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- interpolation
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
function vec2:lerpi(other, amount)
|
||||||
|
self.x = math.lerp(self.x, other.x, amount)
|
||||||
|
self.y = math.lerp(self.y, other.y, amount)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2:lerp(other, amount)
|
||||||
|
return self:copy():lerpi(other, amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- vector products and projections
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
function vec2.dot(a, b)
|
||||||
|
return a.x * b.x + a.y * b.y
|
||||||
|
end
|
||||||
|
|
||||||
|
--"fake", but useful - also called the wedge product apparently
|
||||||
|
function vec2.cross(a, b)
|
||||||
|
return a.x * b.y - a.y * b.x
|
||||||
|
end
|
||||||
|
|
||||||
|
--scalar projection a onto b
|
||||||
|
function vec2.sproj(a, b)
|
||||||
|
local len = b:length()
|
||||||
|
if len == 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
return a:dot(b) / len
|
||||||
|
end
|
||||||
|
|
||||||
|
--vector projection a onto b (writes into a)
|
||||||
|
function vec2.vproji(a, b)
|
||||||
|
local div = b:dot(b)
|
||||||
|
if div == 0 then
|
||||||
|
return a:sset(0,0)
|
||||||
|
end
|
||||||
|
local fac = a:dot(b) / div
|
||||||
|
return a:vset(b):smuli(fac)
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2.vproj(a, b)
|
||||||
|
return a:copy():vproji(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
--vector rejection a onto b (writes into a)
|
||||||
|
function vec2.vreji(a, b)
|
||||||
|
local tx, ty = a.x, a.y
|
||||||
|
a:vproji(b)
|
||||||
|
a:sset(tx - a.x, ty - a.y)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
function vec2.vrej(a, b)
|
||||||
|
return a:copy():vreji(b)
|
||||||
|
end
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
-- vector extension methods for special purposes
|
||||||
|
-- (any common vector ops worth naming)
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
--"physical" friction
|
||||||
|
local _v_friction = vec2:zero() --avoid alloc
|
||||||
|
function vec2:apply_friction(mu, dt)
|
||||||
|
_v_friction:vset(self):smuli(mu * dt)
|
||||||
|
if _v_friction:length_squared() > self:length_squared() then
|
||||||
|
self:sset(0, 0)
|
||||||
|
else
|
||||||
|
self:vsubi(_v_friction)
|
||||||
|
end
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
--"gamey" friction in one dimension
|
||||||
|
function apply_friction_1d(v, mu, dt)
|
||||||
|
local friction = mu * v * dt
|
||||||
|
if math.abs(friction) > math.abs(v) then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return v - friction
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--"gamey" friction in both dimensions
|
||||||
|
function vec2:apply_friction_xy(mu_x, mu_y, dt)
|
||||||
|
self.x = apply_friction_1d(self.x, mu_x, dt)
|
||||||
|
self.y = apply_friction_1d(self.y, mu_y, dt)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
return vec2
|
Loading…
Reference in New Issue
Block a user