mirror of https://gitea.it/1414codeforge/gear
[spec/*,*] Split library in submodules.
parent
e658ba45c7
commit
0e2dd54011
@ -0,0 +1,129 @@
|
|||||||
|
--- General stateless utility algorithms
|
||||||
|
--
|
||||||
|
-- @module gear.algo
|
||||||
|
-- @copyright 2022 The DoubleFourteen Code Forge
|
||||||
|
-- @author Lorenzo Cogotti
|
||||||
|
|
||||||
|
local floor = math.floor
|
||||||
|
local min, max = math.min, math.max
|
||||||
|
|
||||||
|
local algo = {}
|
||||||
|
|
||||||
|
|
||||||
|
--- Clamp x within range [a,b] (where b >= a).
|
||||||
|
--
|
||||||
|
-- @param x (number) value to clamp.
|
||||||
|
-- @param a (number) interval lower bound (inclusive).
|
||||||
|
-- @param b (number) interval upper bound (includive).
|
||||||
|
-- @return (number) clamped value.
|
||||||
|
function algo.clamp(x, a, b) return min(max(x, a), b) end
|
||||||
|
|
||||||
|
--- Fast remove from array.
|
||||||
|
--
|
||||||
|
-- Replace 'array[i]' with last array's element and
|
||||||
|
-- discard array's tail.
|
||||||
|
function algo.removefast(array, i)
|
||||||
|
local n = #array
|
||||||
|
|
||||||
|
array[i] = array[n] -- NOP if i == n
|
||||||
|
array[n] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lt(a, b) return a < b end
|
||||||
|
|
||||||
|
--- Sort array using Insertion Sort - O(n^2).
|
||||||
|
--
|
||||||
|
-- Provides the most basic sorting algorithm around.
|
||||||
|
-- Performs better than regular table.sort() for small arrays
|
||||||
|
-- (~100 elements).
|
||||||
|
--
|
||||||
|
-- @param array (table) array to be sorted.
|
||||||
|
-- @param less (function|nil) comparison function, takes 2 arguments,
|
||||||
|
-- returns true if its first argument is less than its second argument, false otherwise.
|
||||||
|
-- Defaults to operator <.
|
||||||
|
function algo.insertionsort(array, less)
|
||||||
|
less = less or lt
|
||||||
|
|
||||||
|
for i = 2,#array do
|
||||||
|
local val = array[i]
|
||||||
|
local j = i
|
||||||
|
|
||||||
|
while j > 1 and less(val, array[j-1]) do
|
||||||
|
array[j] = array[j-1]
|
||||||
|
j = j - 1
|
||||||
|
end
|
||||||
|
|
||||||
|
array[j] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Binary search last element where
|
||||||
|
-- what <= array[i] - also known as lower bound.
|
||||||
|
--
|
||||||
|
-- @param array (table) an array sorted according to the less function.
|
||||||
|
-- @param what the comparison argument.
|
||||||
|
-- @param less (function|nil) sorting criterium, a function taking 2 arguments,
|
||||||
|
-- returns true if the first argument is less than the second argument,
|
||||||
|
-- false otherwise. Defaults to using the < operator.
|
||||||
|
--
|
||||||
|
-- @return (number) the greatest index i, where what <= array[i].
|
||||||
|
-- If no such element exists, it returns an out of bounds index
|
||||||
|
-- such that array[i] == nil.
|
||||||
|
function algo.bsearchl(array, what, less)
|
||||||
|
less = less or lt
|
||||||
|
|
||||||
|
local lo, hi = 1, #array
|
||||||
|
local ofs, mid = -1, hi
|
||||||
|
|
||||||
|
while mid > 0 do
|
||||||
|
mid = floor(hi / 2)
|
||||||
|
|
||||||
|
-- array[lo+mid] <= what <-> what >= array[lo+mid]
|
||||||
|
-- <-> not what < array[lo+mid]
|
||||||
|
if not less(what, array[lo+mid]) then
|
||||||
|
lo = lo + mid
|
||||||
|
ofs = 0 -- at least one element where array[lo+mid] <= what
|
||||||
|
end
|
||||||
|
|
||||||
|
hi = hi - mid
|
||||||
|
end
|
||||||
|
|
||||||
|
return lo + ofs
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Binary search first element where
|
||||||
|
-- what >= array[i] - also known as upper bound.
|
||||||
|
--
|
||||||
|
-- @param array (array) an array sorted according to the less function.
|
||||||
|
-- @param what the comparison argument.
|
||||||
|
-- @param less (function|nil) sorting criterium, a function taking 2 arguments,
|
||||||
|
-- returns true if the first argument is less than the second argument,
|
||||||
|
-- false otherwise. Defaults to using the < operator.
|
||||||
|
--
|
||||||
|
-- @return (number) the smallest index i, where what >= array[i].
|
||||||
|
-- If no such element exists, it returns an out of bounds index
|
||||||
|
-- such that array[i] == nil.
|
||||||
|
function algo.bsearchr(array, what, less)
|
||||||
|
less = less or lt
|
||||||
|
|
||||||
|
local lo, hi = 1, #array
|
||||||
|
local ofs, mid = -1, hi
|
||||||
|
|
||||||
|
while mid > 0 do
|
||||||
|
mid = floor(hi / 2)
|
||||||
|
|
||||||
|
-- array[lo+mid] >= what <-> not array[lo+mid] < what
|
||||||
|
if not less(array[lo+mid], what) then
|
||||||
|
ofs = 0
|
||||||
|
else
|
||||||
|
lo = lo + mid
|
||||||
|
ofs = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
hi = hi - mid
|
||||||
|
end
|
||||||
|
|
||||||
|
return lo + ofs
|
||||||
|
end
|
||||||
|
|
||||||
|
return algo
|
@ -1,527 +1,21 @@
|
|||||||
--- LÖVE Utility Library
|
--- LÖVE Utility Gear
|
||||||
--
|
--
|
||||||
-- Stateless general purpose functions and utilities.
|
-- General purpose functions and utilities.
|
||||||
-- Provides various basic functionality, including: general utility
|
-- Provides various basic functionality, including: general utility
|
||||||
-- algorithms, linear algebra and simple bounds checking functions.
|
-- algorithms, linear algebra and simple bounds checking functions.
|
||||||
-- Code is reasonably optimized for speed.
|
-- Code is reasonably optimized for speed.
|
||||||
--
|
--
|
||||||
-- @module df-utils
|
-- @module gear
|
||||||
-- @copyright 2022 The DoubleFourteen Code Forge
|
-- @copyright 2022 The DoubleFourteen Code Forge
|
||||||
-- @author Lorenzo Cogotti
|
-- @author Lorenzo Cogotti
|
||||||
|
|
||||||
local utils = {}
|
local BASE = (...)..'.'
|
||||||
|
|
||||||
|
|
||||||
local floor = math.floor
|
return {
|
||||||
local min, max = math.min, math.max
|
algo = require(BASE..'algo'),
|
||||||
local sin, cos = math.sin, math.cos
|
meta = require(BASE..'meta'),
|
||||||
local sqrt = math.sqrt
|
rect = require(BASE..'rect'),
|
||||||
|
strings = require(BASE..'strings'),
|
||||||
--- Clamp x within range [a,b] (where b >= a).
|
vec = require(BASE..'vec')
|
||||||
--
|
}
|
||||||
-- @param x (number) value to clamp.
|
|
||||||
-- @param a (number) interval lower bound (inclusive).
|
|
||||||
-- @param b (number) interval upper bound (includive).
|
|
||||||
-- @return (number) clamped value.
|
|
||||||
function utils.clamp(x, a, b) return min(max(x, a), b) end
|
|
||||||
|
|
||||||
--- Test whether a string starts with a prefix.
|
|
||||||
function utils.startswith(s, prefix)
|
|
||||||
return s:sub(1, #prefix) == prefix
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test whether a string ends with a trailing suffix.
|
|
||||||
function utils.endswith(s, trailing)
|
|
||||||
return trailing == "" or s:sub(-#trailing) == trailing
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Merge table 'from' into table 'to'.
|
|
||||||
--
|
|
||||||
-- For every field in 'from', copy it to destination
|
|
||||||
-- table 'to', whenever the same field is nil in that table.
|
|
||||||
--
|
|
||||||
-- The same process is applied recursively to sub-tables.
|
|
||||||
function utils.mergetable(to, from)
|
|
||||||
for k,v in pairs(from) do
|
|
||||||
if to[k] == nil then
|
|
||||||
to[k] = type(v) == 'table' and utils.mergetable({}, v) or v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return to
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test whether 'obj' is an instance of a given class 'cls'.
|
|
||||||
function utils.isinstance(obj, cls)
|
|
||||||
repeat
|
|
||||||
local m = getmetatable(obj)
|
|
||||||
if m == cls then return true end
|
|
||||||
|
|
||||||
obj = m
|
|
||||||
until obj == nil
|
|
||||||
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
local function opless(a, b) return a < b end
|
|
||||||
|
|
||||||
--- Sort array using Insertion Sort - O(n^2).
|
|
||||||
--
|
|
||||||
-- Provides the most basic sorting algorithm around.
|
|
||||||
-- Performs better than regular table.sort() for small arrays
|
|
||||||
-- (~100 elements).
|
|
||||||
--
|
|
||||||
-- @param array (table) array to be sorted.
|
|
||||||
-- @param less (function|nil) comparison function, takes 2 arguments,
|
|
||||||
-- returns true if its first argument is less than its second argument, false otherwise.
|
|
||||||
-- Defaults to operator <.
|
|
||||||
function utils.insertionsort(array, less)
|
|
||||||
less = less or opless
|
|
||||||
|
|
||||||
for i = 2,#array do
|
|
||||||
local val = array[i]
|
|
||||||
local j = i
|
|
||||||
|
|
||||||
while j > 1 and less(val, array[j-1]) do
|
|
||||||
array[j] = array[j-1]
|
|
||||||
j = j - 1
|
|
||||||
end
|
|
||||||
|
|
||||||
array[j] = val
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Binary search last element where
|
|
||||||
-- what <= array[i] - also known as lower bound.
|
|
||||||
--
|
|
||||||
-- @param array (table) an array sorted according to the less function.
|
|
||||||
-- @param what the comparison argument.
|
|
||||||
-- @param less (function|nil) sorting criterium, a function taking 2 arguments,
|
|
||||||
-- returns true if the first argument is less than the second argument,
|
|
||||||
-- false otherwise. Defaults to using the < operator.
|
|
||||||
--
|
|
||||||
-- @return (number) the greatest index i, where what <= array[i].
|
|
||||||
-- If no such element exists, it returns an out of bounds index
|
|
||||||
-- such that array[i] == nil.
|
|
||||||
function utils.bsearchl(array, what, less)
|
|
||||||
less = less or opless
|
|
||||||
|
|
||||||
local lo, hi = 1, #array
|
|
||||||
local ofs, mid = -1, hi
|
|
||||||
|
|
||||||
while mid > 0 do
|
|
||||||
mid = floor(hi / 2)
|
|
||||||
|
|
||||||
-- array[lo+mid] <= what <-> what >= array[lo+mid]
|
|
||||||
-- <-> not what < array[lo+mid]
|
|
||||||
if not less(what, array[lo+mid]) then
|
|
||||||
lo = lo + mid
|
|
||||||
ofs = 0 -- at least one element where array[lo+mid] <= what
|
|
||||||
end
|
|
||||||
|
|
||||||
hi = hi - mid
|
|
||||||
end
|
|
||||||
|
|
||||||
return lo + ofs
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Binary search first element where
|
|
||||||
-- what >= array[i] - also known as upper bound.
|
|
||||||
--
|
|
||||||
-- @param array (array) an array sorted according to the less function.
|
|
||||||
-- @param what the comparison argument.
|
|
||||||
-- @param less (function|nil) sorting criterium, a function taking 2 arguments,
|
|
||||||
-- returns true if the first argument is less than the second argument,
|
|
||||||
-- false otherwise. Defaults to using the < operator.
|
|
||||||
--
|
|
||||||
-- @return (number) the smallest index i, where what >= array[i].
|
|
||||||
-- If no such element exists, it returns an out of bounds index
|
|
||||||
-- such that array[i] == nil.
|
|
||||||
function utils.bsearchr(array, what, less)
|
|
||||||
less = less or opless
|
|
||||||
|
|
||||||
local lo, hi = 1, #array
|
|
||||||
local ofs, mid = -1, hi
|
|
||||||
|
|
||||||
while mid > 0 do
|
|
||||||
mid = floor(hi / 2)
|
|
||||||
|
|
||||||
-- array[lo+mid] >= what <-> not array[lo+mid] < what
|
|
||||||
if not less(array[lo+mid], what) then
|
|
||||||
ofs = 0
|
|
||||||
else
|
|
||||||
lo = lo + mid
|
|
||||||
ofs = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
hi = hi - mid
|
|
||||||
end
|
|
||||||
|
|
||||||
return lo + ofs
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Fast remove from array.
|
|
||||||
--
|
|
||||||
-- Replace 'array[i]' with last array's element and
|
|
||||||
-- discard array's tail.
|
|
||||||
function utils.removefast(array, i)
|
|
||||||
local n = #array
|
|
||||||
|
|
||||||
array[i] = array[n] -- NOP if i == n
|
|
||||||
array[n] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector dot product.
|
|
||||||
function utils.dot(x1,y1, x2,y2)
|
|
||||||
return x1*x2 + y1*y2
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.dot() equivalent for 3D vector.
|
|
||||||
function utils.dot3(x1,y1,z1, x2,y2,z2)
|
|
||||||
return x1*x2 + y1*y2 + z1*z2
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector squared length.
|
|
||||||
function utils.vecsqrlen(x,y)
|
|
||||||
return x*x + y*y -- utils.dot(x,y, x,y)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.vecsqrlen() equivalent for 3D vectors.
|
|
||||||
function utils.vecsqrlen3(x,y,z)
|
|
||||||
return x*x + y*y + z*z
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector length.
|
|
||||||
function utils.veclen(x,y)
|
|
||||||
return sqrt(x*x + y*y) -- sqrt(utils.vecsqrlen(x,y))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.veclen() equivalent for 3D vectors.
|
|
||||||
function utils.veclen3(x,y,z)
|
|
||||||
return sqrt(x*x + y*y + z*z)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector addition (inline this function when possible).
|
|
||||||
function utils.vecadd(x1,y1, x2,y2)
|
|
||||||
return x1+x2, y1+y2
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.vecadd() equivalent for 3D vectors.
|
|
||||||
function utils.vecadd3(x1,y1,z1, x2,y2,z2)
|
|
||||||
return x1+x2, y1+y2, z1+z2
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector subtraction (inline this function when possible).
|
|
||||||
function utils.vecsub(x1,y1, x2,y2)
|
|
||||||
return x1-x2, y1-y2
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.vecsub() equivalent for 3D vectors.
|
|
||||||
function utils.vecsub3(x1,y1,z1, x2,y2,z2)
|
|
||||||
return x1-x2, y1-y2, z1-z2
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector scale (inline this function when possible).
|
|
||||||
function utils.vecscale(x,y, s)
|
|
||||||
return x*s, y*s
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.vecscale() equivalent for 3D vectors.
|
|
||||||
function utils.vecscale3(x,y,z, s)
|
|
||||||
return x*s, y*s, z*s
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector division by scalar (inline this function when possible).
|
|
||||||
function utils.vecdiv(x,y, s)
|
|
||||||
return x/s, y/s
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.vecdiv() equivalent for 3D vectors.
|
|
||||||
function utils.vecdiv3(x,y,z, s)
|
|
||||||
return x/s, y/s, z/s
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Vector fused multiply add (inline this function when possible).
|
|
||||||
--
|
|
||||||
-- @return the first vector, added to the second vector scaled by a factor.
|
|
||||||
function utils.vecma(x1,y1, s, x2,y2)
|
|
||||||
return x1 + x2*s, y1 + y2*s
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.vecma() equivalent for 3D vectors.
|
|
||||||
function utils.vecma3(x1,y1,z1, s, x2,y2,z2)
|
|
||||||
return x1 + x2*s, y1 + y2*s, z1 + z2*s
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test vectors for equality with optional epsilon.
|
|
||||||
function utils.veceq(x1,y1, x2,y2, eps)
|
|
||||||
local abs = math.abs
|
|
||||||
|
|
||||||
eps = eps or 0.001
|
|
||||||
|
|
||||||
return abs(x1-x2) < eps and abs(y1-y2) < eps
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.veceq() equivalent for 3D vectors.
|
|
||||||
function utils.veceq3(x1,y1,z1, x2,y2,z2, eps)
|
|
||||||
local abs = math.abs
|
|
||||||
|
|
||||||
eps = eps or 0.001
|
|
||||||
|
|
||||||
return abs(x1-x2) < eps and
|
|
||||||
abs(y1-y2) < eps and
|
|
||||||
abs(z1-z2) < eps
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Normalize vector.
|
|
||||||
--
|
|
||||||
-- @return (x,y, len) normalized components and vector's original length.
|
|
||||||
function utils.normalize(x,y)
|
|
||||||
local len = sqrt(x*x + y*y) -- utils.veclen(x,y)
|
|
||||||
|
|
||||||
if len < 1.0e-4 then
|
|
||||||
return x,y, 0
|
|
||||||
end
|
|
||||||
|
|
||||||
return x / len, y / len, len
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.normalize() equivalent for 3D vectors.
|
|
||||||
function utils.normalize3(x,y,z)
|
|
||||||
local len = sqrt(x*x + y*y + z*z)
|
|
||||||
|
|
||||||
if len < 1.0e-4 then
|
|
||||||
return x,y,z, 0
|
|
||||||
end
|
|
||||||
|
|
||||||
return x / len, y / len, z / len, len
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Calculate the squared distance between two vectors/points.
|
|
||||||
function utils.sqrdist(x1,y1, x2,y2)
|
|
||||||
local dx,dy = x2-x1, y2-y1
|
|
||||||
|
|
||||||
return dx*dx, dy*dy -- utils.vecsqrlen(dx,dy)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.sqrdist() equivalent for 3D vectors.
|
|
||||||
function utils.sqrdist3(x1,y1,z1, x2,y2,z2)
|
|
||||||
local dx,dy,dz = x2-x1, y2-y1, z2-z1
|
|
||||||
|
|
||||||
return dx*dx, dy*dy, dz*dz
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Calculate the distance between two vectors/points.
|
|
||||||
function utils.distance(x1,y1, x2,y2)
|
|
||||||
local dx,dy = x2-x1, y2-y1
|
|
||||||
|
|
||||||
return sqrt(dx*dx + dy*dy) -- sqrt(utils.sqrdist(x1,y1, x2,y2))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- utils.distance() equivalent for 3D vectors/points.
|
|
||||||
function utils.distance3(x1,y1,z1, x2,y2,z2)
|
|
||||||
local dx,dy,dz = x2-x1, y2-y1, z2-z1
|
|
||||||
|
|
||||||
return sqrt(dx*dx + dy*dy + dz*dz)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Calculate and return the union of two rectangles.
|
|
||||||
function utils.rectunion(x1,y1,w1,h1, x2,y2,w2,h2)
|
|
||||||
local xw1,yh1, xw2,yh2
|
|
||||||
|
|
||||||
if w1 < 0 or h1 < 0 then
|
|
||||||
local huge = math.huge
|
|
||||||
|
|
||||||
x1,y1,xw1,yh1 = huge,huge,-huge,-huge
|
|
||||||
else
|
|
||||||
xw1,yh1 = x1 + w1,y1 + h1
|
|
||||||
end
|
|
||||||
if w2 < 0 or h2 < 0 then
|
|
||||||
local huge = math.huge
|
|
||||||
|
|
||||||
x2,y2,xw2,yh2 = huge,huge,-huge,-huge
|
|
||||||
else
|
|
||||||
xw2,yh2 = x2 + w2,y2 + h2
|
|
||||||
end
|
|
||||||
|
|
||||||
x1 = min(x1, x2)
|
|
||||||
y1 = min(y1, y2)
|
|
||||||
xw1 = max(xw1, xw2)
|
|
||||||
xh1 = max(xh1, xh2)
|
|
||||||
|
|
||||||
return x1, y1, xw1 - x1, yh1 - y1
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Extend rectangle to include a point.
|
|
||||||
function utils.rectexpand(x,y,w,h, px,py)
|
|
||||||
if w < 0 or h < 0 then
|
|
||||||
return px,py,0,0
|
|
||||||
end
|
|
||||||
|
|
||||||
local xw, yh
|
|
||||||
x = min(x, px)
|
|
||||||
y = min(y, py)
|
|
||||||
xw = max(x+w, px)
|
|
||||||
yh = max(y+h, py)
|
|
||||||
|
|
||||||
return x, y, xw-x, yh-y
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Calculate and return the intersection between two rectangles.
|
|
||||||
function utils.rectintersection(x1,y1,w1,h1, x2,y2,w2,h2)
|
|
||||||
if w1 < 0 or h1 < 0 then
|
|
||||||
return x1,y1,w1,h1
|
|
||||||
elseif w2 < 0 or h2 < 0 then
|
|
||||||
return x2,y2,w2,h2
|
|
||||||
end
|
|
||||||
|
|
||||||
local xw1,yh1 = x1+w1, y1+h1
|
|
||||||
local xw2,yh2 = x2+w2, y2+h2
|
|
||||||
|
|
||||||
x1 = max(x1, x2)
|
|
||||||
y1 = max(y1, y2)
|
|
||||||
xw1 = min(xw1, xw2)
|
|
||||||
yh1 = min(yh1, yh2)
|
|
||||||
return x1,y1, xw1-x1,yh1-y1
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test whether point (x,y) lies inside a rectangle.
|
|
||||||
function utils.pointinrect(x,y, rx,ry,rw,rh)
|
|
||||||
return x >= rx and y >= ry and x-rx <= rw and y-ry <= rh
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test whether the first rectangle lies inside the second.
|
|
||||||
function utils.rectinside(x1,y1,w1,h1, x2,y2,w2,h2)
|
|
||||||
return (x1 >= x2 and y1 >= y2 and w1 <= w2 and h1 <= h2 and w2 >= 0 and h2 >= 0)
|
|
||||||
or ((w1 < 0 or h1 < 0) and (w2 >= 0 and h2 >= 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test two rectangles for equality with optional epsilon.
|
|
||||||
function utils.recteq(x1,y1,w1,h1, x2,y2,w2,h2, eps)
|
|
||||||
local abs = math.abs
|
|
||||||
|
|
||||||
eps = eps or 0.007
|
|
||||||
|
|
||||||
return (abs(x1 - x2) <= eps and
|
|
||||||
abs(y1 - y2) <= eps and
|
|
||||||
abs(w1 - w2) <= eps and
|
|
||||||
abs(h1 - h2) <= eps)
|
|
||||||
or ((w1 < 0 or h1 < 0) and (w2 < 0 or h2 < 0))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Test whether a rectangle is empty.
|
|
||||||
function utils.rectempty(x,y,w,h)
|
|
||||||
return w < 0 or h < 0
|
|
||||||
end
|
|
||||||
|
|
||||||
local function rotatesincos(px,py, sina,cosa, ox,oy)
|
|
||||||
return ox + cosa*px - sina*py,
|
|
||||||
oy + sina*px + cosa*py
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Rotate point (px,py) around (ox,oy) about rot radians.
|
|
||||||
function utils.rotatepoint(px,py, rot, ox,oy)
|
|
||||||
ox = ox or 0
|
|
||||||
oy = oy or 0
|
|
||||||
|
|
||||||
local sina,cosa = sin(rot), cos(rot)
|
|
||||||
|
|
||||||
return rotatesincos(px,py, sina,cosa, ox,oy)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Rotate an axis-aligned rectangle around (ox,oy) about rot radians,
|
|
||||||
-- and return the result's minimum enclosing
|
|
||||||
-- axis-aligned rectangle.
|
|
||||||
--
|
|
||||||
-- NOTE: This causes precision loss, possibly generating
|
|
||||||
-- larger bounds than needed for the rotated geometry
|
|
||||||
-- Don't use this function repeatedly on the same bounds.
|
|
||||||
function utils.rotatebounds(bx,by,bw,bh, rot, ox,oy)
|
|
||||||
if bw < 0 or bh < 0 then
|
|
||||||
return bx,by,bw,bh
|
|
||||||
end
|
|
||||||
|
|
||||||
ox = ox or 0
|
|
||||||
oy = oy or 0
|
|
||||||
|
|
||||||
local sina,cosa = sin(rot), cos(rot)
|
|
||||||
|
|
||||||
local x1,y1 = rotatesincos(bx, by, sina,cosa, ox,oy)
|
|
||||||
local x2,y2 = rotatesincos(bx+bw, by, sina,cosa, ox,oy)
|
|
||||||
local x3,y3 = rotatesincos(bx+bw, by+bh, sina,cosa, ox,oy)
|
|
||||||
local x4,y4 = rotatesincos(bx, by+bh, sina,cosa, ox,oy)
|
|
||||||
|
|
||||||
local rx = min(min(min(x1, x2), x3), x4)
|
|
||||||
local ry = min(min(min(y1, y2), y3), y4)
|
|
||||||
local rxw = max(max(max(x1, x2), x3), x4)
|
|
||||||
local ryh = max(max(max(y1, y2), y3), y4)
|
|
||||||
|
|
||||||
return rx,ry, rxw-rx,ryh-ry
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Transform world coordinates to screen coordinates.
|
|
||||||
--
|
|
||||||
-- @param x (number) World coordinate X.
|
|
||||||
-- @param y (number) World coordinate Y.
|
|
||||||
-- @param vx (number|nil) Point of view X coordinate, defaults to w/2.
|
|
||||||
-- @param vy (number|nil) Point of view Y coordinate, defaults to h/2.
|
|
||||||
-- @param rot (number|nil) View rotation in radians, defaults to 0.
|
|
||||||
-- @param scale (number|nil) View scale (zoom), defaults to 1.
|
|
||||||
-- @param left (number|nil) Viewport left corner, defaults to 0.
|
|
||||||
-- @param top (number|nil) Viewport top corner, defaults to 0.
|
|
||||||
-- @param w (number|nil) Viewport width, defaults to love.graphics.getWidth().
|
|
||||||
-- @param h (number|nil) Viewport height, defaults to love.graphics.getHeight().
|
|
||||||
--
|
|
||||||
-- @return (x,y) Transformed to screen coordinates according to
|
|
||||||
-- viewport and offset.
|
|
||||||
function utils.toscreencoords(x,y, vx,vy, rot, scale, left,top, w,h)
|
|
||||||
left,top = left or 0, top or 0
|
|
||||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
|
||||||
|
|
||||||
local halfw,halfh = w/2, h/2
|
|
||||||
|
|
||||||
vx,vy = vx or halfw, vy or halfh
|
|
||||||
rot = rot or 0
|
|
||||||
scale = scale or 1
|
|
||||||
|
|
||||||
local sina,cosa = sin(rot), cos(rot)
|
|
||||||
|
|
||||||
x,y = x - vx, y - vy
|
|
||||||
x,y = cosa*x - sina*y, sina*x + cosa*y
|
|
||||||
return x*scale + halfw + left, y*scale + halfh + top
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Transform screen coordinates to world coordinates.
|
|
||||||
--
|
|
||||||
-- @param x (number) Screen coordinate X.
|
|
||||||
-- @param y (number) Screen coordinate Y.
|
|
||||||
-- @param vx (number|nil) Point of view X coordinate, defaults to w/2.
|
|
||||||
-- @param vy (number|nil) Point of view Y coordinate, defaults to h/2.
|
|
||||||
-- @param rot (number|nil) View rotation in radians, defaults to 0.
|
|
||||||
-- @param scale (number|nil) View scale (zoom), defaults to 1.
|
|
||||||
-- @param left (number|nil) Viewport left corner, defaults to 0.
|
|
||||||
-- @param top (number|nil) Viewport top corner, defaults to 0.
|
|
||||||
-- @param w (number|nil) Viewport width, defaults to love.graphics.getWidth().
|
|
||||||
-- @param h (number|nil) Viewport height, defaults to love.graphics.getHeight().
|
|
||||||
--
|
|
||||||
-- @return (x,y) Transformed to world coordinates according to
|
|
||||||
-- viewport and offset.
|
|
||||||
function utils.toworldcoords(x,y, vx,vy, rot, scale, left,top,w,h)
|
|
||||||
left, top = left or 0, top or 0
|
|
||||||
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
|
||||||
|
|
||||||
local halfw,halfh = w/2, h/2
|
|
||||||
|
|
||||||
vx,vy = vx or halfw, vy or halfh
|
|
||||||
rot = rot or 0
|
|
||||||
scale = scale or 1
|
|
||||||
|
|
||||||
local sina,cosa = sin(-rot), cos(-rot)
|
|
||||||
|
|
||||||
x,y = (x - halfw - left) / scale, (y - halfh - top) / scale
|
|
||||||
x,y = cosa*x - sina*y, sina*x + cosa*y
|
|
||||||
return x+vx, y+vy
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return utils
|
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
--- Functions dealing with metatables and tables merging.
|
||||||
|
--
|
||||||
|
-- @module gear.meta
|
||||||
|
-- @copyright 2022 The DoubleFourteen Code Forge
|
||||||
|
-- @author Lorenzo Cogotti
|
||||||
|
|
||||||
|
local meta = {}
|
||||||
|
|
||||||
|
|
||||||
|
--- Test whether 'obj' is an instance of a given class 'cls'.
|
||||||
|
function meta.isinstance(obj, cls)
|
||||||
|
repeat
|
||||||
|
local m = getmetatable(obj)
|
||||||
|
if m == cls then return true end
|
||||||
|
|
||||||
|
obj = m
|
||||||
|
until obj == nil
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Merge table 'from' into table 'to'.
|
||||||
|
--
|
||||||
|
-- For every field in 'from', copy it to destination
|
||||||
|
-- table 'to', whenever the same field is nil in that table.
|
||||||
|
--
|
||||||
|
-- The same process is applied recursively to sub-tables.
|
||||||
|
function meta.mergetable(to, from)
|
||||||
|
for k,v in pairs(from) do
|
||||||
|
if to[k] == nil then
|
||||||
|
to[k] = type(v) == 'table' and meta.mergetable({}, v) or v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return to
|
||||||
|
end
|
||||||
|
|
||||||
|
return meta
|
@ -0,0 +1,135 @@
|
|||||||
|
--- Axis-aligned rectangles.
|
||||||
|
--
|
||||||
|
-- Function for basic bounding rectangles building and testing.
|
||||||
|
--
|
||||||
|
-- @module gear.rect
|
||||||
|
-- @copyright 2022 The DoubleFourteen Code Forge
|
||||||
|
-- @author Lorenzo Cogotti
|
||||||
|
|
||||||
|
local rotatesincos = require((...):gsub('rect', '')..'vec').rotatesincos
|
||||||
|
|
||||||
|
local min, max = math.min, math.max
|
||||||
|
local abs = math.abs
|
||||||
|
|
||||||
|
local rect = {}
|
||||||
|
|
||||||
|
|
||||||
|
--- Extend rectangle to include a point.
|
||||||
|
function rect.expand(x,y,w,h, px,py)
|
||||||
|
if w < 0 or h < 0 then
|
||||||
|
return px,py,0,0
|
||||||
|
end
|
||||||
|
|
||||||
|
local xw, yh
|
||||||
|
x = min(x, px)
|
||||||
|
y = min(y, py)
|
||||||
|
xw = max(x+w, px)
|
||||||
|
yh = max(y+h, py)
|
||||||
|
|
||||||
|
return x, y, xw-x, yh-y
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculate and return the union of two rectangles.
|
||||||
|
function rect.union(x1,y1,w1,h1, x2,y2,w2,h2)
|
||||||
|
local xw1,yh1, xw2,yh2
|
||||||
|
|
||||||
|
if w1 < 0 or h1 < 0 then
|
||||||
|
local huge = math.huge
|
||||||
|
|
||||||
|
x1,y1,xw1,yh1 = huge,huge,-huge,-huge
|
||||||
|
else
|
||||||
|
xw1,yh1 = x1 + w1,y1 + h1
|
||||||
|
end
|
||||||
|
if w2 < 0 or h2 < 0 then
|
||||||
|
local huge = math.huge
|
||||||
|
|
||||||
|
x2,y2,xw2,yh2 = huge,huge,-huge,-huge
|
||||||
|
else
|
||||||
|
xw2,yh2 = x2 + w2,y2 + h2
|
||||||
|
end
|
||||||
|
|
||||||
|
x1 = min(x1, x2)
|
||||||
|
y1 = min(y1, y2)
|
||||||
|
xw1 = max(xw1, xw2)
|
||||||
|
xh1 = max(xh1, xh2)
|
||||||
|
|
||||||
|
return x1, y1, xw1 - x1, yh1 - y1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculate and return the intersection between two rectangles.
|
||||||
|
function rect.intersection(x1,y1,w1,h1, x2,y2,w2,h2)
|
||||||
|
if w1 < 0 or h1 < 0 then
|
||||||
|
return x1,y1,w1,h1
|
||||||
|
elseif w2 < 0 or h2 < 0 then
|
||||||
|
return x2,y2,w2,h2
|
||||||
|
end
|
||||||
|
|
||||||
|
local xw1,yh1 = x1+w1, y1+h1
|
||||||
|
local xw2,yh2 = x2+w2, y2+h2
|
||||||
|
|
||||||
|
x1 = max(x1, x2)
|
||||||
|
y1 = max(y1, y2)
|
||||||
|
xw1 = min(xw1, xw2)
|
||||||
|
yh1 = min(yh1, yh2)
|
||||||
|
return x1,y1, xw1-x1,yh1-y1
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rotate rectangle around (ox,oy) about rot radians,
|
||||||
|
-- and return the result's minimum enclosing
|
||||||
|
-- axis-aligned rectangle.
|
||||||
|
--
|
||||||
|
-- NOTE: This causes precision loss, possibly generating
|
||||||
|
-- larger bounds than needed for the rotated geometry
|
||||||
|
-- Don't use this function repeatedly on the same bounds.
|
||||||
|
function rect.rotate(rx,ry,rw,rh, rot, ox,oy)
|
||||||
|
if rw < 0 or rh < 0 then
|
||||||
|
return rx,ry,rw,rh
|
||||||
|
end
|
||||||
|
|
||||||
|
ox = ox or 0
|
||||||
|
oy = oy or 0
|
||||||
|
|
||||||
|
local sina,cosa = sin(rot),cos(rot)
|
||||||
|
|
||||||
|
local x1,y1 = rotatesincos(rx, ry, sina,cosa, ox,oy)
|
||||||
|
local x2,y2 = rotatesincos(rx+rw, ry, sina,cosa, ox,oy)
|
||||||
|
local x3,y3 = rotatesincos(rx+rw, ry+rh, sina,cosa, ox,oy)
|
||||||
|
local x4,y4 = rotatesincos(rx, ry+rh, sina,cosa, ox,oy)
|
||||||
|
|
||||||
|
local rxw, rxh
|
||||||
|
rx = min(min(min(x1, x2), x3), x4)
|
||||||
|
ry = min(min(min(y1, y2), y3), y4)
|
||||||
|
rxw = max(max(max(x1, x2), x3), x4)
|
||||||
|
ryh = max(max(max(y1, y2), y3), y4)
|
||||||
|
|
||||||
|
return rx,ry, rxw-rx,ryh-ry
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test whether point (x,y) lies inside a rectangle.
|
||||||
|
function rect.pointinside(x,y, rx,ry,rw,rh)
|
||||||
|
return x >= rx and y >= ry and x-rx <= rw and y-ry <= rh
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test whether the first rectangle lies inside the second.
|
||||||
|
function rect.rectinside(x1,y1,w1,h1, x2,y2,w2,h2)
|
||||||
|
return (x1 >= x2 and y1 >= y2 and w1 <= w2 and h1 <= h2 and w2 >= 0 and h2 >= 0)
|
||||||
|
or ((w1 < 0 or h1 < 0) and (w2 >= 0 and h2 >= 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test two rectangles for equality with optional epsilon.
|
||||||
|
function rect.eq(x1,y1,w1,h1, x2,y2,w2,h2, eps)
|
||||||
|
eps = eps or 0.007
|
||||||
|
|
||||||
|
return (abs(x1 - x2) <= eps and
|
||||||
|
abs(y1 - y2) <= eps and
|
||||||
|
abs(w1 - w2) <= eps and
|
||||||
|
abs(h1 - h2) <= eps)
|
||||||
|
or ((w1 < 0 or h1 < 0) and (w2 < 0 or h2 < 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test whether a rectangle is empty.
|
||||||
|
function rect.isempty(x,y,w,h)
|
||||||
|
return w < 0 or h < 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return rect
|
@ -0,0 +1,216 @@
|
|||||||
|
require 'busted.runner'()
|
||||||
|
|
||||||
|
describe("gear", function()
|
||||||
|
|
||||||
|
setup(function()
|
||||||
|
gear = require 'init'
|
||||||
|
algo = gear.algo
|
||||||
|
rect = gear.rect
|
||||||
|
math = require 'math'
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("insertion sort #sort", function()
|
||||||
|
local insertionsort = algo.insertionsort
|
||||||
|
|
||||||
|
it("sorts arrays", function()
|
||||||
|
local elems = {}
|
||||||
|
local expect = {}
|
||||||
|
|
||||||
|
for n = 2,512 do
|
||||||
|
for i = 1,n do
|
||||||
|
elems[i] = math.random(-32768, 32767)
|
||||||
|
expect[i] = elems[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(expect)
|
||||||
|
insertionsort(elems)
|
||||||
|
|
||||||
|
assert.are.same(expect, elems)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("has no effect on single element array", function()
|
||||||
|
|
||||||
|
for i = 1,10 do
|
||||||
|
local val = (math.random() - 0.5) * 1024
|
||||||
|
local expect = { val }
|
||||||
|
local elems = { val }
|
||||||
|
|
||||||
|
insertionsort(elems)
|
||||||
|
|
||||||
|
assert.are.same(expect, elems)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("does nothing on empty array", function()
|
||||||
|
local expect = {}
|
||||||
|
local elems = {}
|
||||||
|
|
||||||
|
insertionsort(elems)
|
||||||
|
|
||||||
|
assert.are.same(expect, elems)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("binary search #bsearch", function()
|
||||||
|
local bsearchl = algo.bsearchl
|
||||||
|
local bsearchr = algo.bsearchr
|
||||||
|
|
||||||
|
it("behaves properly on empty arrays", function()
|
||||||
|
local empty = {}
|
||||||
|
|
||||||
|
local idx = bsearchr(empty, 1)
|
||||||
|
assert.is_true(empty[idx] == nil)
|
||||||
|
|
||||||
|
idx = bsearchl(empty, 1)
|
||||||
|
assert.is_true(empty[idx] == nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("finds elements within sorted arrays", function()
|
||||||
|
local dims = { 1, 2, 64, 512, 1024 }
|
||||||
|
|
||||||
|
for _,n in ipairs(dims) do
|
||||||
|
local elems = {}
|
||||||
|
|
||||||
|
for i = 1,n do
|
||||||
|
elems[#elems+1] = ((math.random() - 0.5) * 4096)
|
||||||
|
end
|
||||||
|
|
||||||
|
local mustfind = {}
|
||||||
|
for i = 1,10 do
|
||||||
|
mustfind[#mustfind+1] = elems[math.random(1,#elems)]
|
||||||
|
end
|
||||||
|
|
||||||
|
local mustnotfind = {
|
||||||
|
4097,
|
||||||
|
-4097,
|
||||||
|
-math.huge,
|
||||||
|
math.huge,
|
||||||
|
16386,
|
||||||
|
-16386
|
||||||
|
}
|
||||||
|
|
||||||
|
table.sort(elems)
|
||||||
|
|
||||||
|
for _,v in ipairs(mustfind) do
|
||||||
|
local idx = bsearchl(elems, v)
|
||||||
|
assert.equal(v, elems[idx])
|
||||||
|
assert.is_false(elems[idx+1] ~= nil and elems[idx+1] <= v)
|
||||||
|
|
||||||
|
idx = bsearchr(elems, v)
|
||||||
|
assert.equal(v, elems[idx])
|
||||||
|
assert.is_false(elems[idx+1] ~= nil and elems[idx+1] <= v)
|
||||||
|
end
|
||||||
|
for _,v in ipairs(mustnotfind) do
|
||||||
|
local idx = bsearchl(elems, v)
|
||||||
|
assert.not_equal(v, elems[idx])
|
||||||
|
if v < elems[1] then
|
||||||
|
assert.is_true(elems[idx] == nil)
|
||||||
|
else
|
||||||
|
assert.is_true(elems[idx] <= v)
|
||||||
|
end
|
||||||
|
|
||||||
|
idx = bsearchr(elems, v)
|
||||||
|
assert.not_equal(v, elems[idx])
|
||||||
|
if v > elems[#elems] then
|
||||||
|
assert.is_true(elems[idx] == nil)
|
||||||
|
else
|
||||||
|
assert.is_true(elems[idx] >= v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("rect #bounds", function()
|
||||||
|
local bigreal = 9999999.0
|
||||||
|
local min,max = math.min, math.max
|
||||||
|
local pointinrect = rect.pointinside
|
||||||
|
local rectempty = rect.isempty
|
||||||
|
local recteq = rect.eq
|
||||||
|
local rectexpand = rect.expand
|
||||||
|
local rectinside = rect.rectinside
|
||||||
|
local rectintersection = rect.intersection
|
||||||
|
local rectunion = rect.union
|
||||||
|
|
||||||
|
it("is empty if its dimensions are negative", function()
|
||||||
|
assert.is_true(rectempty(0,0,-1,-1))
|
||||||
|
assert.is_true(rectempty(bigreal,bigreal,-bigreal,-bigreal))
|
||||||
|
assert.is_true(rectempty(-bigreal,-bigreal,-bigreal,-bigreal))
|
||||||
|
assert.is_true(rectempty(0,0,bigreal,-1))
|
||||||
|
assert.is_true(rectempty(0,0,0,-1))
|
||||||
|
assert.is_true(rectempty(0,0,-1,bigreal))
|
||||||
|
assert.is_true(rectempty(0,0,-1,0))
|
||||||
|
|
||||||
|
assert.is_false(rectempty(0,0,0,0))
|
||||||
|
assert.is_false(rectempty(0,0,-0,0))
|
||||||
|
assert.is_false(rectempty(bigreal,bigreal,bigreal,bigreal))
|
||||||
|
end)
|
||||||
|
it("doesn't contain anything if empty", function()
|
||||||
|
local x,y,w,h = 0,0,-1,-1
|
||||||
|
|
||||||
|
assert.is_false(rectinside(0,0,0,0, x,y,w,h))
|
||||||
|
assert.is_false(rectinside(x,y,w,h, x,y,w,h))
|
||||||
|
assert.is_false(rectinside(0,0,bigreal,bigreal, x,y,w,h))
|
||||||
|
assert.is_false(rectinside(0,0,-bigreal,-bigreal, x,y,w,h))
|
||||||
|
end)
|
||||||
|
it("always contains empty rect if non-empty", function()
|
||||||
|
assert.is_true(rectinside(0,0,-1,-1, 0,0,0,0))
|
||||||
|
assert.is_true(rectinside(bigreal,bigreal,-bigreal,-bigreal, 0,0,0,0))
|
||||||
|
assert.is_true(rectinside(bigreal,bigreal,bigreal,-bigreal, 0,0,0,0))
|
||||||
|
assert.is_true(rectinside(bigreal,bigreal,-bigreal,bigreal, 0,0,0,0))
|
||||||
|
assert.is_true(rectinside(bigreal,bigreal,-1.0e-5,bigreal, 0,0,0,0))
|
||||||
|
end)
|
||||||
|
it("may contain a single point", function()
|
||||||
|
for i,pt in ipairs({{0,0}, {bigreal,bigreal}, {-bigreal,-bigreal}}) do
|
||||||
|
local px,py = pt[1], pt[2]
|
||||||
|
local x,y,w,h = 0,0,-1,-1
|
||||||
|
|
||||||
|
x,y,w,h = rectexpand(x,y,w,h, px,py)
|
||||||
|
assert.is_false(rectempty(x,y,w,h))
|
||||||
|
assert.is_true(pointinrect(px,py, x,y,w,h))
|
||||||
|
assert.is_true(recteq(x,y,w,h, px,py,0,0))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
it("may expand arbitrarily to contain more points", function()
|
||||||
|
local points = {}
|
||||||
|
|
||||||
|
local xmin,ymin = math.huge, math.huge
|
||||||
|
local xmax,ymax = -math.huge,-math.huge
|
||||||
|
for i = 1,16535 do
|
||||||
|
local x = (math.random() - 0.5) * 50
|
||||||
|
local y = (math.random() - 0.5) * 50
|
||||||
|
|
||||||
|
points[#points+1] = { x, y }
|
||||||
|
xmin = min(xmin, x)
|
||||||
|
ymin = min(ymin, y)
|
||||||
|
xmax = max(xmax, x)
|
||||||
|
ymax = max(ymax, y)
|
||||||
|
end
|
||||||
|
|
||||||
|
local x,y,w,h = 0,0,-1,-1
|
||||||
|
for i,pt in ipairs(points) do
|
||||||
|
local px,py = pt[1], pt[2]
|
||||||
|
x,y,w,h = rectexpand(x,y,w,h, px,py)
|
||||||
|
|
||||||
|
assert.is_true(pointinrect(px,py, x,y,w,h))
|
||||||
|
end
|
||||||
|
|
||||||
|
local ex,ey,ew,eh = xmin,ymin, xmax-xmin,ymax-ymin
|
||||||
|
assert.is_false(rectempty(x,y,w,h))
|
||||||
|
assert.is_true(recteq(x,y,w,h, ex,ey,ew,eh, 0.1))
|
||||||
|
end)
|
||||||
|
it("may expand arbitrarily to contain more rects", function()
|
||||||
|
pending("to be tested...")
|
||||||
|
end)
|
||||||
|
it("may be used to enclose arbitrary geometry", function()
|
||||||
|
pending("to be tested...")
|
||||||
|
end)
|
||||||
|
it("may be tested against other rects", function()
|
||||||
|
pending("to be tested...")
|
||||||
|
end)
|
||||||
|
it("may be intersected with other rects", function()
|
||||||
|
pending("to be tested...")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
@ -0,0 +1,28 @@
|
|||||||
|
local strings = {}
|
||||||
|
|
||||||
|
|
||||||
|
--- Test whether a string starts with a prefix.
|
||||||
|
function strings.startswith(s, prefix)
|
||||||
|
-- optimized version of: return s:sub(1, #prefix) == prefix
|
||||||
|
for i = 1,#prefix do
|
||||||
|
if s:byte(i) ~= prefix:byte(i) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test whether a string ends with a trailing suffix.
|
||||||
|
function strings.endswith(s, trailing)
|
||||||
|
-- optimized version of: return trailing == "" or s:sub(-#trailing) == trailing
|
||||||
|
local n1,n2 = #s,#trailing
|
||||||
|
|
||||||
|
for i = 0,n2-1 do
|
||||||
|
if s:byte(n1-i) ~= trailing:byte(n2-i) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
return strings
|
@ -0,0 +1,254 @@
|
|||||||
|
--- Vector algebra.
|
||||||
|
--
|
||||||
|
-- Functions implementing basic 2D and 3D vector algebra.
|
||||||
|
-- Code is reasonably optimized for speed.
|
||||||
|
--
|
||||||
|
-- @module gear.vec
|
||||||
|
-- @copyright 2022 The DoubleFourteen Code Forge
|
||||||
|
-- @author Lorenzo Cogotti
|
||||||
|
|
||||||
|
local min, max = math.min, math.max
|
||||||
|
local sin, cos = math.sin, math.cos
|
||||||
|
local abs = math.abs
|
||||||
|
local sqrt = math.sqrt
|
||||||
|
|
||||||
|
local vec = {}
|
||||||
|
|
||||||
|
|
||||||
|
--- Vector dot product.
|
||||||
|
function vec.dot(x1,y1, x2,y2)
|
||||||
|
return x1*x2 + y1*y2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.dot() equivalent for 3D vector.
|
||||||
|
function vec.dot3(x1,y1,z1, x2,y2,z2)
|
||||||
|
return x1*x2 + y1*y2 + z1*z2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector squared length.
|
||||||
|
function vec.sqrlen(x,y)
|
||||||
|
return x*x + y*y -- vec.dot(x,y, x,y)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.sqrlen() equivalent for 3D vectors.
|
||||||
|
function vec.sqrlen3(x,y,z)
|
||||||
|
return x*x + y*y + z*z
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector length.
|
||||||
|
function vec.len(x,y)
|
||||||
|
return sqrt(x*x + y*y) -- sqrt(vec.sqrlen(x,y))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.len() equivalent for 3D vectors.
|
||||||
|
function vec.len3(x,y,z)
|
||||||
|
return sqrt(x*x + y*y + z*z)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector addition.
|
||||||
|
function vec.add(x1,y1, x2,y2)
|
||||||
|
return x1+x2, y1+y2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.add() equivalent for 3D vectors.
|
||||||
|
function vec.add3(x1,y1,z1, x2,y2,z2)
|
||||||
|
return x1+x2, y1+y2, z1+z2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector subtraction.
|
||||||
|
function vec.sub(x1,y1, x2,y2)
|
||||||
|
return x1-x2, y1-y2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.sub() equivalent for 3D vectors.
|
||||||
|
function vec.sub3(x1,y1,z1, x2,y2,z2)
|
||||||
|
return x1-x2, y1-y2, z1-z2
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector scale.
|
||||||
|
function vec.scale(x,y, s)
|
||||||
|
return x*s, y*s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.scale() equivalent for 3D vectors.
|
||||||
|
function vec.scale3(x,y,z, s)
|
||||||
|
return x*s, y*s, z*s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector division by scalar.
|
||||||
|
function vec.div(x,y, s)
|
||||||
|
return x/s, y/s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.div() equivalent for 3D vectors.
|
||||||
|
function vec.div3(x,y,z, s)
|
||||||
|
return x/s, y/s, z/s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Vector multiply add.
|
||||||
|
--
|
||||||
|
-- @return the first vector, added to the second vector scaled by a factor.
|
||||||
|
function vec.madd(x1,y1, s, x2,y2)
|
||||||
|
return x1 + x2*s, y1 + y2*s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.madd() equivalent for 3D vectors.
|
||||||
|
function vec.madd3(x1,y1,z1, s, x2,y2,z2)
|
||||||
|
return x1 + x2*s, y1 + y2*s, z1 + z2*s
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Test vectors for equality with optional epsilon.
|
||||||
|
function vec.eq(x1,y1, x2,y2, eps)
|
||||||
|
eps = eps or 0.001
|
||||||
|
|
||||||
|
return abs(x1-x2) < eps and abs(y1-y2) < eps
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.eq() equivalent for 3D vectors.
|
||||||
|
function vec.eq3(x1,y1,z1, x2,y2,z2, eps)
|
||||||
|
eps = eps or 0.001
|
||||||
|
|
||||||
|
return abs(x1-x2) < eps and
|
||||||
|
abs(y1-y2) < eps and
|
||||||
|
abs(z1-z2) < eps
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Normalize vector.
|
||||||
|
--
|
||||||
|
-- @return (x,y, len) normalized components and vector's original length.
|
||||||
|
function vec.normalize(x,y)
|
||||||
|
local len = sqrt(x*x + y*y) -- vec.len(x,y)
|
||||||
|
|
||||||
|
if len < 1.0e-4 then
|
||||||
|
return x,y, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return x / len, y / len, len
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.normalize() equivalent for 3D vectors.
|
||||||
|
function vec.normalize3(x,y,z)
|
||||||
|
local len = sqrt(x*x + y*y + z*z)
|
||||||
|
|
||||||
|
if len < 1.0e-4 then
|
||||||
|
return x,y,z, 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return x / len, y / len, z / len, len
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculate the squared distance between two vectors/points.
|
||||||
|
function vec.sqrdist(x1,y1, x2,y2)
|
||||||
|
local dx,dy = x2-x1, y2-y1
|
||||||
|
|
||||||
|
return dx*dx, dy*dy -- vec.sqrlen(dx,dy)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.sqrdist() equivalent for 3D vectors.
|
||||||
|
function vec.sqrdist3(x1,y1,z1, x2,y2,z2)
|
||||||
|
local dx,dy,dz = x2-x1, y2-y1, z2-z1
|
||||||
|
|
||||||
|
return dx*dx, dy*dy, dz*dz
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Calculate the distance between two vectors/points.
|
||||||
|
function vec.dist(x1,y1, x2,y2)
|
||||||
|
local dx,dy = x2-x1, y2-y1
|
||||||
|
|
||||||
|
return sqrt(dx*dx + dy*dy) -- sqrt(vec.sqrdist(x1,y1, x2,y2))
|
||||||
|
end
|
||||||
|
|
||||||
|
--- vec.dist() equivalent for 3D vectors/points.
|
||||||
|
function vec.dist3(x1,y1,z1, x2,y2,z2)
|
||||||
|
local dx,dy,dz = x2-x1, y2-y1, z2-z1
|
||||||
|
|
||||||
|
return sqrt(dx*dx + dy*dy + dz*dz)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rotate point (px,py) around (ox,oy) by the provided
|
||||||
|
-- sine and cosine.
|
||||||
|
--
|
||||||
|
-- This function should only be used for (valuable)
|
||||||
|
-- optimization purposes.
|
||||||
|
function vec.rotatesincos(px,py, sina,cosa, ox,oy)
|
||||||
|
return ox + cosa*px - sina*py,
|
||||||
|
oy + sina*px + cosa*py
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Rotate point (px,py) around (ox,oy) about rot radians.
|
||||||
|
function vec.rotatepoint(px,py, rot, ox,oy)
|
||||||
|
ox = ox or 0
|
||||||
|
oy = oy or 0
|
||||||
|
|
||||||
|
local sina,cosa = sin(rot),cos(rot)
|
||||||
|
|
||||||
|
-- vec.rotatesincos(px,py, sina,cosa, ox,oy)
|
||||||
|
return ox + cosa*px - sina*py,
|
||||||
|
oy + sina*px + cosa*py
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Transform world coordinates to screen coordinates.
|
||||||
|
--
|
||||||
|
-- @param x (number) World coordinate X.
|
||||||
|
-- @param y (number) World coordinate Y.
|
||||||
|
-- @param vx (number|nil) Point of view X coordinate, defaults to w/2.
|
||||||
|
-- @param vy (number|nil) Point of view Y coordinate, defaults to h/2.
|
||||||
|
-- @param rot (number|nil) View rotation in radians, defaults to 0.
|
||||||
|
-- @param scale (number|nil) View scale (zoom), defaults to 1.
|
||||||
|
-- @param left (number|nil) Viewport left corner, defaults to 0.
|
||||||
|
-- @param top (number|nil) Viewport top corner, defaults to 0.
|
||||||
|
-- @param w (number|nil) Viewport width, defaults to love.graphics.getWidth().
|
||||||
|
-- @param h (number|nil) Viewport height, defaults to love.graphics.getHeight().
|
||||||
|
--
|
||||||
|
-- @return (x,y) Transformed to screen coordinates according to
|
||||||
|
-- viewport and offset.
|
||||||
|
function vec.toscreencoords(x,y, vx,vy, rot, scale, left,top, w,h)
|
||||||
|
left,top = left or 0, top or 0
|
||||||
|
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||||
|
|
||||||
|
local halfw,halfh = w/2, h/2
|
||||||
|
|
||||||
|
vx,vy = vx or halfw, vy or halfh
|
||||||
|
rot = rot or 0
|
||||||
|
scale = scale or 1
|
||||||
|
|
||||||
|
local sina,cosa = sin(rot),cos(rot)
|
||||||
|
|
||||||
|
x,y = x - vx, y - vy
|
||||||
|
x,y = cosa*x - sina*y, sina*x + cosa*y
|
||||||
|
return x*scale + halfw + left, y*scale + halfh + top
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Transform screen coordinates to world coordinates.
|
||||||
|
--
|
||||||
|
-- @param x (number) Screen coordinate X.
|
||||||
|
-- @param y (number) Screen coordinate Y.
|
||||||
|
-- @param vx (number|nil) Point of view X coordinate, defaults to w/2.
|
||||||
|
-- @param vy (number|nil) Point of view Y coordinate, defaults to h/2.
|
||||||
|
-- @param rot (number|nil) View rotation in radians, defaults to 0.
|
||||||
|
-- @param scale (number|nil) View scale (zoom), defaults to 1.
|
||||||
|
-- @param left (number|nil) Viewport left corner, defaults to 0.
|
||||||
|
-- @param top (number|nil) Viewport top corner, defaults to 0.
|
||||||
|
-- @param w (number|nil) Viewport width, defaults to love.graphics.getWidth().
|
||||||
|
-- @param h (number|nil) Viewport height, defaults to love.graphics.getHeight().
|
||||||
|
--
|
||||||
|
-- @return (x,y) Transformed to world coordinates according to
|
||||||
|
-- viewport and offset.
|
||||||
|
function vec.toworldcoords(x,y, vx,vy, rot, scale, left,top,w,h)
|
||||||
|
left, top = left or 0, top or 0
|
||||||
|
w,h = w or love.graphics.getWidth(), h or love.graphics.getHeight()
|
||||||
|
|
||||||
|
local halfw,halfh = w/2, h/2
|
||||||
|
|
||||||
|
vx,vy = vx or halfw, vy or halfh
|
||||||
|
rot = rot or 0
|
||||||
|
scale = scale or 1
|
||||||
|
|
||||||
|
local sina,cosa = sin(-rot),cos(-rot)
|
||||||
|
|
||||||
|
x,y = (x - halfw - left) / scale, (y - halfh - top) / scale
|
||||||
|
x,y = cosa*x - sina*y, sina*x + cosa*y
|
||||||
|
return x+vx, y+vy
|
||||||
|
end
|
||||||
|
|
||||||
|
return vec
|
Loading…
Reference in New Issue