From 4f380d8afe90ead3d96bb61532c2a3e6af654d37 Mon Sep 17 00:00:00 2001 From: Max Cahill <1bardesign@gmail.com> Date: Mon, 12 Apr 2021 13:04:08 +1000 Subject: [PATCH 1/9] [added] `functional.stitch` and `functional.cycle` specialised mapping variants --- functional.lua | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/functional.lua b/functional.lua index 6f077ca..2726b1f 100644 --- a/functional.lua +++ b/functional.lua @@ -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)] + 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 ----------------------------------------------------------- From 6eb979a68c0d946f52ee2598c2fb2a75dbcfcb3d Mon Sep 17 00:00:00 2001 From: Max Cahill <1bardesign@gmail.com> Date: Mon, 12 Apr 2021 16:13:46 +1000 Subject: [PATCH 2/9] [fixed] off-by-one in functional.cycle --- functional.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functional.lua b/functional.lua index 2726b1f..801868c 100644 --- a/functional.lua +++ b/functional.lua @@ -209,7 +209,7 @@ functional.map_stitch = functional.stitch function functional.cycle(t, f) local result = {} for i, a in ipairs(t) do - local b = t[mathx.wrap(i + 1, 1, #t)] + local b = t[mathx.wrap(i + 1, 1, #t + 1)] local v = f(a, b, i) if v ~= nil then table.insert(result, v) From ab0839430a3c1ee0d00575b125f32ddcaaa7bf91 Mon Sep 17 00:00:00 2001 From: Max Cahill Date: Tue, 13 Apr 2021 15:38:40 +1000 Subject: [PATCH 3/9] [fixed] broken intersect.circle_circle_collide result --- intersect.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intersect.lua b/intersect.lua index b661e70..decf533 100644 --- a/intersect.lua +++ b/intersect.lua @@ -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 From bfad0baba94d805cfd1d79acf83390048c69b16f Mon Sep 17 00:00:00 2001 From: Max Cahill Date: Tue, 13 Apr 2021 15:39:36 +1000 Subject: [PATCH 4/9] [added] experimental `intersect.bounce_off` (needs to be verified) --- intersect.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/intersect.lua b/intersect.lua index decf533..673ba13 100644 --- a/intersect.lua +++ b/intersect.lua @@ -418,4 +418,15 @@ function intersect.point_in_poly(point, poly) return wn ~= 0 end +--resolution helpers + +--bounce a velocity off of a normal (modifying velocity) +local _bounce_temp = vec2() +function intersect.bounce_off(velocity, normal) + _bounce_temp:vset(velocity) + velocity:vreji(normal) + velocity:vsubi(_bounce_temp:vsubi(velocity)) + return velocity +end + return intersect From c840bd01a55b7169874a70aae2bfcbf436071c52 Mon Sep 17 00:00:00 2001 From: Max Cahill Date: Wed, 14 Apr 2021 13:32:14 +1000 Subject: [PATCH 5/9] [added] more intersection/resolution helpers --- intersect.lua | 53 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/intersect.lua b/intersect.lua index 673ba13..5740734 100644 --- a/intersect.lua +++ b/intersect.lua @@ -420,13 +420,58 @@ 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) -local _bounce_temp = vec2() -function intersect.bounce_off(velocity, normal) - _bounce_temp:vset(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) - velocity:vsubi(_bounce_temp:vsubi(velocity)) + --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 From 62957a9270bba37cb37e22de721b8a5a646726c4 Mon Sep 17 00:00:00 2001 From: Max Cahill Date: Wed, 14 Apr 2021 17:06:31 +1000 Subject: [PATCH 6/9] [modified] PR #17 a little; simplified various unneeded sections and added comments --- stringx.lua | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/stringx.lua b/stringx.lua index b0568ab..10e189f 100644 --- a/stringx.lua +++ b/stringx.lua @@ -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 From d476bc9c9c4ea7d0e9d32df3679bca1e4f782240 Mon Sep 17 00:00:00 2001 From: Max Cahill <1bardesign@gmail.com> Date: Thu, 15 Apr 2021 10:21:41 +1000 Subject: [PATCH 7/9] [modified] how `sequence` binds the functional interface to make binding new methods less error prone and shorter --- sequence.lua | 56 +++++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/sequence.lua b/sequence.lua index 4dcb722..8222af5 100644 --- a/sequence.lua +++ b/sequence.lua @@ -53,7 +53,35 @@ function sequence:copy(...) return sequence(table.copy(self, ...)) end ---import functional interface +--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 + +--aliases +for _, v in ipairs({ + {"remap", "map_inplace"}, + {"map_stitch", "stitch"}, + {"map_cycle", "cycle"}, +}) do + sequence[v[1]] = sequence[v[2]] +end + +--(less common cases where we don't want to construct a new sequence or have more than one return value) function sequence:foreach(f) return functional.foreach(self, f) end @@ -62,35 +90,9 @@ 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)) -end - 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 From d1b7e77b8a4553ed628909480d03bfb90a40b1ee Mon Sep 17 00:00:00 2001 From: Max Cahill <1bardesign@gmail.com> Date: Thu, 15 Apr 2021 10:22:26 +1000 Subject: [PATCH 8/9] [modified] `vec2` to support direct use for conversion in `functional.map` --- vec2.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vec2.lua b/vec2.lua index 31657ac..7769742 100644 --- a/vec2.lua +++ b/vec2.lua @@ -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 From 87834e92675270e2322fc8777c4e1c26b5999a5a Mon Sep 17 00:00:00 2001 From: Max Cahill <1bardesign@gmail.com> Date: Thu, 15 Apr 2021 10:31:06 +1000 Subject: [PATCH 9/9] [modified] sequence wrappers a bit more; exposed functional query interface --- sequence.lua | 78 +++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/sequence.lua b/sequence.lua index 8222af5..6964e83 100644 --- a/sequence.lua +++ b/sequence.lua @@ -24,33 +24,27 @@ 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)) -end - -function sequence:dedupe() - return sequence(table.dedupe(self)) -end - -function sequence:collapse() - return sequence(table.collapse(self)) -end -sequence.flatten = sequence.collapse - -function sequence:append(...) - return sequence(table.append(self, ...)) -end - -function sequence:overlay(...) - return sequence(table.overlay(self, ...)) -end - -function sequence:copy(...) - return sequence(table.copy(self, ...)) +--aliases +for _, v in ipairs({ + {"flatten", "collapse"}, +}) do + sequence[v[1]] = sequence[v[2]] end --import functional interface in method form @@ -72,24 +66,40 @@ for _, v in ipairs({ end end +--(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 + + --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 ---(less common cases where we don't want to construct a new sequence or have more than one return value) -function sequence:foreach(f) - return functional.foreach(self, f) -end - -function sequence:reduce(seed, f) - return functional.reduce(self, seed, f) -end - +--(anything that needs bespoke wrapping) function sequence:partition(f) local a, b = functional.partition(self, f) return sequence(a), sequence(b)