Merge remote-tracking branch 'origin/master' into feature/insert-sorted-binary-search

This commit is contained in:
Max Cahill 2021-04-30 11:22:11 +10:00
commit 5601d9415b
5 changed files with 190 additions and 71 deletions

View File

@ -13,6 +13,7 @@
local path = (...):gsub("functional", "")
local tablex = require(path .. "tablex")
local mathx = require(path .. "mathx")
local functional = setmetatable({}, {
__index = tablex,
@ -172,6 +173,54 @@ function functional.zip(t1, t2, f)
return ret
end
-----------------------------------------------------------
--specialised maps
-- (experimental: let me know if you have better names for these!)
-----------------------------------------------------------
--maps a sequence {a, b, c} -> collapse { f(a), f(b), f(c) }
-- (ie results from functions should generally be sequences,
-- which are appended onto each other, resulting in one big sequence)
-- (automatically drops any nils, same as map)
function functional.stitch(t, f)
local result = {}
for i, v in ipairs(t) do
local v = f(v, i)
if v ~= nil then
if type(v) == "table" then
for _, e in ipairs(v) do
table.insert(result, e)
end
else
table.insert(result, v)
end
end
end
return result
end
--alias
functional.map_stitch = functional.stitch
--maps a sequence {a, b, c} -> { f(a, b), f(b, c), f(c, a) }
-- useful for inter-dependent data
-- (automatically drops any nils, same as map)
function functional.cycle(t, f)
local result = {}
for i, a in ipairs(t) do
local b = t[mathx.wrap(i + 1, 1, #t + 1)]
local v = f(a, b, i)
if v ~= nil then
table.insert(result, v)
end
end
return result
end
functional.map_cycle = functional.cycle
-----------------------------------------------------------
--generating data
-----------------------------------------------------------

View File

@ -55,7 +55,7 @@ function intersect.circle_circle_collide(a_pos, a_rad, b_pos, b_rad, into)
into = vec2:zero()
end
--normalise, scale to separating distance
into:vset(_ccc_delta):sdiv(dist):smuli(rad - dist)
into:vset(_ccc_delta):sdivi(dist):smuli(rad - dist)
return into
end
return false
@ -97,7 +97,7 @@ function intersect._line_displacement_to_sep(a_start, a_end, separation, total_r
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()
separation:vset(a_end):vsubi(a_start):normalisei():rot90li()
else
separation:smuli(-sep)
end
@ -418,4 +418,60 @@ function intersect.point_in_poly(point, poly)
return wn ~= 0
end
--resolution helpers
--resolve a collision between two bodies, given a (minimum) separating vector
-- from a's frame of reference, like the result of any of the _collide functions
--requires the two positions of the bodies, the msv, and a balance factor
--balance should be between 1 and 0;
-- 1 is only a_pos moving to resolve
-- 0 is only b_pos moving to resolve
-- 0.5 is balanced between both (default)
--note: this wont work as-is for line segments, which have two separate position coordinates
-- you will need to understand what is going on and move the second coordinate yourself
function intersect.resolve_msv(a_pos, b_pos, msv, balance)
balance = balance or 0.5
a_pos:fmai(msv, balance)
b_pos:fmai(msv, -(1 - balance))
end
--bounce a velocity off of a normal (modifying velocity)
--essentially flips the part of the velocity in the direction of the normal
function intersect.bounce_off(velocity, normal, conservation)
--(default)
conservation = conservation or 1
--take a copy, we need it
local old_vel = vec2.pooled_copy(velocity)
--reject on the normal (keep velocity tangential to the normal)
velocity:vreji(normal)
--add back the complement of the difference;
--basically "flip" the velocity in line with the normal.
velocity:fmai(old_vel:vsubi(velocity), -conservation)
--clean up
old_vel:release()
return velocity
end
--mutual bounce; two similar bodies bounce off each other, transferring energy
function intersect.mutual_bounce(velocity_a, velocity_b, normal, conservation)
--(default)
conservation = conservation or 1
--take copies, we need them
local old_a_vel = vec2.pooled_copy(velocity_a)
local old_b_vel = vec2.pooled_copy(velocity_b)
--reject on the normal
velocity_a:vreji(normal)
velocity_b:vreji(normal)
--calculate the amount remaining from the old velocity
--(transfer ownership)
local a_remaining = old_a_vel:vsubi(velocity_a)
local b_remaining = old_b_vel:vsubi(velocity_b)
--transfer it to the other body
velocity_a:fmai(b_remaining, conservation)
velocity_b:fmai(a_remaining, conservation)
--clean up
a_remaining:release()
b_remaining:release()
end
return intersect

View File

@ -24,73 +24,85 @@ sequence.sort = stable_sort
--patch various interfaces in a type-preserving way, for method chaining
--import copying tablex
function sequence:keys()
return sequence(table.keys(self))
--(common case where something returns another sequence for chaining)
for _, v in ipairs({
"keys",
"values",
"dedupe",
"collapse",
"append",
"overlay",
"copy",
}) do
local table_f = table[v]
sequence[v] = function(self, ...)
return sequence(table_f(self, ...))
end
end
function sequence:values()
return sequence(table.values(self))
--aliases
for _, v in ipairs({
{"flatten", "collapse"},
}) do
sequence[v[1]] = sequence[v[2]]
end
function sequence:dedupe()
return sequence(table.dedupe(self))
--import functional interface in method form
--(common case where something returns another sequence for chaining)
for _, v in ipairs({
"map",
"map_inplace",
"filter",
"filter_inplace",
"remove_if",
"zip",
"stitch",
"cycle",
}) do
local functional_f = functional[v]
sequence[v] = function(self, ...)
return sequence(functional_f(self, ...))
end
end
function sequence:collapse()
return sequence(table.collapse(self))
end
sequence.flatten = sequence.collapse
function sequence:append(...)
return sequence(table.append(self, ...))
--(cases where we don't want to construct a new sequence)
for _, v in ipairs({
"foreach",
"reduce",
"any",
"none",
"all",
"count",
"contains",
"sum",
"mean",
"minmax",
"max",
"min",
"find_min",
"find_max",
"find_nearest",
"find_match",
}) do
sequence[v] = functional[v]
end
function sequence:overlay(...)
return sequence(table.overlay(self, ...))
end
function sequence:copy(...)
return sequence(table.copy(self, ...))
end
--import functional interface
function sequence:foreach(f)
return functional.foreach(self, f)
end
function sequence:reduce(seed, f)
return functional.reduce(self, seed, f)
end
function sequence:map(f)
return sequence(functional.map(self, f))
end
function sequence:map_inplace(f)
return sequence(functional.map_inplace(self, f))
end
sequence.remap = sequence.map_inplace
function sequence:filter(f)
return sequence(functional.filter(self, f))
end
function sequence:filter_inplace(f)
return sequence(functional.filter_inplace(self, f))
end
function sequence:remove_if(f)
return sequence(functional.remove_if(self, f))
--aliases
for _, v in ipairs({
{"remap", "map_inplace"},
{"map_stitch", "stitch"},
{"map_cycle", "cycle"},
{"find_best", "find_max"},
}) do
sequence[v[1]] = sequence[v[2]]
end
--(anything that needs bespoke wrapping)
function sequence:partition(f)
local a, b = functional.partition(self, f)
return sequence(a), sequence(b)
end
function sequence:zip(other, f)
return sequence(functional.zip(self, other, f))
end
return sequence

View File

@ -215,11 +215,8 @@ function stringx.trim(s)
return s:sub(head, tail)
end
--trim the start of a string
function stringx.ltrim(s)
if s == "" or s == string.rep(" ", s:len()) then
return ""
end
local head = 1
for i = 1, #s do
if not _whitespace_bytes[s:byte(i)] then
@ -227,13 +224,14 @@ function stringx.ltrim(s)
break
end
end
if head == 1 then
return s
end
return s:sub(head)
end
--trim the end of a string
function stringx.rtrim(s)
if s == "" or s == string.rep(" ", s:len()) then
return ""
end
local tail = #s
for i = #s, 1, -1 do
@ -243,6 +241,10 @@ function stringx.rtrim(s)
end
end
if tail == #s then
return s
end
return s:sub(1, tail)
end
@ -322,13 +324,13 @@ function stringx.starts_with(s, prefix)
return true
end
function stringx.ends_with(s, posfix)
if posfix == "" then return true end
if #posfix > #s then return false end
for i = 0, #posfix-1 do
if s:byte(#s-i) ~= posfix:byte(#posfix-i) then
--check if a given string ends with another
--(without garbage)
function stringx.ends_with(s, suffix)
local len = #s
local suffix_len = #suffix
for i = 0, suffix_len - 1 do
if s:byte(len - i) ~= suffix:byte(suffix_len - i) then
return false
end
end

View File

@ -16,7 +16,7 @@ end
--probably-too-flexible ctor
function vec2:new(x, y)
if x and y then
if type(x) == "number" and type(y) == "number" then
return vec2:xy(x,y)
elseif x then
if type(x) == "number" then