rewrite math module to global, improve test suite
This commit is contained in:
parent
ae4af71822
commit
3239d6ac95
732
modules/math+/math.lua
Normal file
732
modules/math+/math.lua
Normal file
@ -0,0 +1,732 @@
|
||||
-- ======================================================================
|
||||
-- ENHANCED CONSTANTS (higher precision)
|
||||
-- ======================================================================
|
||||
|
||||
math.pi = 3.14159265358979323846 -- Replace with higher precision
|
||||
math.tau = 6.28318530717958647693 -- 2*pi, useful for full rotations
|
||||
math.e = 2.71828182845904523536
|
||||
math.phi = 1.61803398874989484820 -- Golden ratio (1 + sqrt(5)) / 2
|
||||
math.sqrt2 = 1.41421356237309504880
|
||||
math.sqrt3 = 1.73205080756887729353
|
||||
math.ln2 = 0.69314718055994530942 -- Natural log of 2
|
||||
math.ln10 = 2.30258509299404568402 -- Natural log of 10
|
||||
math.infinity = 1/0
|
||||
math.nan = 0/0
|
||||
|
||||
-- ======================================================================
|
||||
-- EXTENDED FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Cube root that handles negative numbers correctly
|
||||
function math.cbrt(x)
|
||||
return x < 0 and -(-x)^(1/3) or x^(1/3)
|
||||
end
|
||||
|
||||
-- Euclidean distance - more accurate than sqrt(x*x + y*y)
|
||||
function math.hypot(x, y)
|
||||
return math.sqrt(x * x + y * y)
|
||||
end
|
||||
|
||||
-- IEEE 754 NaN check
|
||||
function math.isnan(x)
|
||||
return x ~= x
|
||||
end
|
||||
|
||||
-- Check if number is finite
|
||||
function math.isfinite(x)
|
||||
return x > -math.infinity and x < math.infinity
|
||||
end
|
||||
|
||||
-- Mathematical sign function
|
||||
function math.sign(x)
|
||||
return x > 0 and 1 or (x < 0 and -1 or 0)
|
||||
end
|
||||
|
||||
-- Constrain value to range
|
||||
function math.clamp(x, min, max)
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
-- Linear interpolation
|
||||
function math.lerp(a, b, t)
|
||||
return a + (b - a) * t
|
||||
end
|
||||
|
||||
-- Smooth interpolation using Hermite polynomial
|
||||
function math.smoothstep(a, b, t)
|
||||
t = math.clamp((t - a) / (b - a), 0, 1)
|
||||
return t * t * (3 - 2 * t)
|
||||
end
|
||||
|
||||
-- Map value from input range to output range
|
||||
function math.map(x, in_min, in_max, out_min, out_max)
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
end
|
||||
|
||||
-- Round to nearest integer
|
||||
function math.round(x)
|
||||
return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)
|
||||
end
|
||||
|
||||
-- Round to specified decimal places
|
||||
function math.roundto(x, decimals)
|
||||
local mult = 10 ^ (decimals or 0)
|
||||
return math.floor(x * mult + 0.5) / mult
|
||||
end
|
||||
|
||||
-- Normalize angle to [-π, π] range
|
||||
function math.normalize_angle(angle)
|
||||
return angle - 2 * math.pi * math.floor((angle + math.pi) / (2 * math.pi))
|
||||
end
|
||||
|
||||
-- 2D Euclidean distance
|
||||
function math.distance(x1, y1, x2, y2)
|
||||
local dx, dy = x2 - x1, y2 - y1
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
-- Factorial with bounds checking
|
||||
function math.factorial(n)
|
||||
if n < 0 or n ~= math.floor(n) or n > 170 then
|
||||
return nil
|
||||
end
|
||||
local result = 1
|
||||
for i = 2, n do
|
||||
result = result * i
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Greatest common divisor using Euclidean algorithm
|
||||
function math.gcd(a, b)
|
||||
a, b = math.floor(math.abs(a)), math.floor(math.abs(b))
|
||||
while b ~= 0 do
|
||||
a, b = b, a % b
|
||||
end
|
||||
return a
|
||||
end
|
||||
|
||||
-- Least common multiple
|
||||
function math.lcm(a, b)
|
||||
if a == 0 or b == 0 then return 0 end
|
||||
return math.abs(a * b) / math.gcd(a, b)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- ENHANCED RANDOM FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Random float in range
|
||||
function math.randomf(min, max)
|
||||
if not min and not max then
|
||||
return math.random()
|
||||
elseif not max then
|
||||
max = min
|
||||
min = 0
|
||||
end
|
||||
return min + math.random() * (max - min)
|
||||
end
|
||||
|
||||
-- Random integer in range
|
||||
function math.randint(min, max)
|
||||
if not max then
|
||||
max = min
|
||||
min = 1
|
||||
end
|
||||
return math.floor(math.random() * (max - min + 1) + min)
|
||||
end
|
||||
|
||||
-- Random boolean with probability
|
||||
function math.randboolean(p)
|
||||
p = p or 0.5
|
||||
return math.random() < p
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- STATISTICS FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
function math.sum(t)
|
||||
if type(t) ~= "table" then return 0 end
|
||||
local sum = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
sum = sum + t[i]
|
||||
end
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
function math.mean(t)
|
||||
if type(t) ~= "table" or #t == 0 then return 0 end
|
||||
local sum = 0
|
||||
local count = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
sum = sum + t[i]
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count > 0 and sum / count or 0
|
||||
end
|
||||
|
||||
function math.median(t)
|
||||
if type(t) ~= "table" or #t == 0 then return 0 end
|
||||
local nums = {}
|
||||
local count = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
count = count + 1
|
||||
nums[count] = t[i]
|
||||
end
|
||||
end
|
||||
if count == 0 then return 0 end
|
||||
table.sort(nums)
|
||||
if count % 2 == 0 then
|
||||
return (nums[count/2] + nums[count/2 + 1]) / 2
|
||||
else
|
||||
return nums[math.ceil(count/2)]
|
||||
end
|
||||
end
|
||||
|
||||
function math.variance(t)
|
||||
if type(t) ~= "table" then return 0 end
|
||||
local count = 0
|
||||
local m = math.mean(t)
|
||||
local sum = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
local dev = t[i] - m
|
||||
sum = sum + dev * dev
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count > 1 and sum / count or 0
|
||||
end
|
||||
|
||||
function math.stdev(t)
|
||||
return math.sqrt(math.variance(t))
|
||||
end
|
||||
|
||||
function math.pvariance(t)
|
||||
if type(t) ~= "table" then return 0 end
|
||||
local count = 0
|
||||
local m = math.mean(t)
|
||||
local sum = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
local dev = t[i] - m
|
||||
sum = sum + dev * dev
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count > 0 and sum / count or 0
|
||||
end
|
||||
|
||||
function math.pstdev(t)
|
||||
return math.sqrt(math.pvariance(t))
|
||||
end
|
||||
|
||||
function math.mode(t)
|
||||
if type(t) ~= "table" or #t == 0 then return nil end
|
||||
local counts = {}
|
||||
local most_frequent = nil
|
||||
local max_count = 0
|
||||
for i=1, #t do
|
||||
local v = t[i]
|
||||
counts[v] = (counts[v] or 0) + 1
|
||||
if counts[v] > max_count then
|
||||
max_count = counts[v]
|
||||
most_frequent = v
|
||||
end
|
||||
end
|
||||
return most_frequent
|
||||
end
|
||||
|
||||
function math.minmax(t)
|
||||
if type(t) ~= "table" or #t == 0 then return nil, nil end
|
||||
local min, max
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
min = t[i]
|
||||
max = t[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
if min == nil then return nil, nil end
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
if t[i] < min then min = t[i] end
|
||||
if t[i] > max then max = t[i] end
|
||||
end
|
||||
end
|
||||
return min, max
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- 2D VECTOR OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
math.vec2 = {
|
||||
new = function(x, y)
|
||||
return {x = x or 0, y = y or 0}
|
||||
end,
|
||||
|
||||
copy = function(v)
|
||||
return {x = v.x, y = v.y}
|
||||
end,
|
||||
|
||||
add = function(a, b)
|
||||
return {x = a.x + b.x, y = a.y + b.y}
|
||||
end,
|
||||
|
||||
sub = function(a, b)
|
||||
return {x = a.x - b.x, y = a.y - b.y}
|
||||
end,
|
||||
|
||||
mul = function(a, b)
|
||||
if type(b) == "number" then
|
||||
return {x = a.x * b, y = a.y * b}
|
||||
end
|
||||
return {x = a.x * b.x, y = a.y * b.y}
|
||||
end,
|
||||
|
||||
div = function(a, b)
|
||||
if type(b) == "number" then
|
||||
local inv = 1 / b
|
||||
return {x = a.x * inv, y = a.y * inv}
|
||||
end
|
||||
return {x = a.x / b.x, y = a.y / b.y}
|
||||
end,
|
||||
|
||||
dot = function(a, b)
|
||||
return a.x * b.x + a.y * b.y
|
||||
end,
|
||||
|
||||
length = function(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y)
|
||||
end,
|
||||
|
||||
length_squared = function(v)
|
||||
return v.x * v.x + v.y * v.y
|
||||
end,
|
||||
|
||||
distance = function(a, b)
|
||||
local dx, dy = b.x - a.x, b.y - a.y
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
end,
|
||||
|
||||
distance_squared = function(a, b)
|
||||
local dx, dy = b.x - a.x, b.y - a.y
|
||||
return dx * dx + dy * dy
|
||||
end,
|
||||
|
||||
normalize = function(v)
|
||||
local len = math.sqrt(v.x * v.x + v.y * v.y)
|
||||
if len > 1e-10 then
|
||||
local inv_len = 1 / len
|
||||
return {x = v.x * inv_len, y = v.y * inv_len}
|
||||
end
|
||||
return {x = 0, y = 0}
|
||||
end,
|
||||
|
||||
rotate = function(v, angle)
|
||||
local c, s = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
x = v.x * c - v.y * s,
|
||||
y = v.x * s + v.y * c
|
||||
}
|
||||
end,
|
||||
|
||||
angle = function(v)
|
||||
return math.atan2(v.y, v.x)
|
||||
end,
|
||||
|
||||
lerp = function(a, b, t)
|
||||
t = math.clamp(t, 0, 1)
|
||||
return {
|
||||
x = a.x + (b.x - a.x) * t,
|
||||
y = a.y + (b.y - a.y) * t
|
||||
}
|
||||
end,
|
||||
|
||||
reflect = function(v, normal)
|
||||
local dot = v.x * normal.x + v.y * normal.y
|
||||
return {
|
||||
x = v.x - 2 * dot * normal.x,
|
||||
y = v.y - 2 * dot * normal.y
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- 3D VECTOR OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
math.vec3 = {
|
||||
new = function(x, y, z)
|
||||
return {x = x or 0, y = y or 0, z = z or 0}
|
||||
end,
|
||||
|
||||
copy = function(v)
|
||||
return {x = v.x, y = v.y, z = v.z}
|
||||
end,
|
||||
|
||||
add = function(a, b)
|
||||
return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z}
|
||||
end,
|
||||
|
||||
sub = function(a, b)
|
||||
return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z}
|
||||
end,
|
||||
|
||||
mul = function(a, b)
|
||||
if type(b) == "number" then
|
||||
return {x = a.x * b, y = a.y * b, z = a.z * b}
|
||||
end
|
||||
return {x = a.x * b.x, y = a.y * b.y, z = a.z * b.z}
|
||||
end,
|
||||
|
||||
div = function(a, b)
|
||||
if type(b) == "number" then
|
||||
local inv = 1 / b
|
||||
return {x = a.x * inv, y = a.y * inv, z = a.z * inv}
|
||||
end
|
||||
return {x = a.x / b.x, y = a.y / b.y, z = a.z / b.z}
|
||||
end,
|
||||
|
||||
dot = function(a, b)
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z
|
||||
end,
|
||||
|
||||
cross = function(a, b)
|
||||
return {
|
||||
x = a.y * b.z - a.z * b.y,
|
||||
y = a.z * b.x - a.x * b.z,
|
||||
z = a.x * b.y - a.y * b.x
|
||||
}
|
||||
end,
|
||||
|
||||
length = function(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||
end,
|
||||
|
||||
length_squared = function(v)
|
||||
return v.x * v.x + v.y * v.y + v.z * v.z
|
||||
end,
|
||||
|
||||
distance = function(a, b)
|
||||
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
||||
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
||||
end,
|
||||
|
||||
distance_squared = function(a, b)
|
||||
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
||||
return dx * dx + dy * dy + dz * dz
|
||||
end,
|
||||
|
||||
normalize = function(v)
|
||||
local len = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||
if len > 1e-10 then
|
||||
local inv_len = 1 / len
|
||||
return {x = v.x * inv_len, y = v.y * inv_len, z = v.z * inv_len}
|
||||
end
|
||||
return {x = 0, y = 0, z = 0}
|
||||
end,
|
||||
|
||||
lerp = function(a, b, t)
|
||||
t = math.clamp(t, 0, 1)
|
||||
return {
|
||||
x = a.x + (b.x - a.x) * t,
|
||||
y = a.y + (b.y - a.y) * t,
|
||||
z = a.z + (b.z - a.z) * t
|
||||
}
|
||||
end,
|
||||
|
||||
reflect = function(v, normal)
|
||||
local dot = v.x * normal.x + v.y * normal.y + v.z * normal.z
|
||||
return {
|
||||
x = v.x - 2 * dot * normal.x,
|
||||
y = v.y - 2 * dot * normal.y,
|
||||
z = v.z - 2 * dot * normal.z
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- MATRIX OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
math.mat2 = {
|
||||
new = function(a, b, c, d)
|
||||
return {
|
||||
{a or 1, b or 0},
|
||||
{c or 0, d or 1}
|
||||
}
|
||||
end,
|
||||
|
||||
identity = function()
|
||||
return {{1, 0}, {0, 1}}
|
||||
end,
|
||||
|
||||
mul = function(a, b)
|
||||
return {
|
||||
{
|
||||
a[1][1] * b[1][1] + a[1][2] * b[2][1],
|
||||
a[1][1] * b[1][2] + a[1][2] * b[2][2]
|
||||
},
|
||||
{
|
||||
a[2][1] * b[1][1] + a[2][2] * b[2][1],
|
||||
a[2][1] * b[1][2] + a[2][2] * b[2][2]
|
||||
}
|
||||
}
|
||||
end,
|
||||
|
||||
det = function(m)
|
||||
return m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
||||
end,
|
||||
|
||||
inverse = function(m)
|
||||
local det = m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
||||
if math.abs(det) < 1e-10 then
|
||||
return nil
|
||||
end
|
||||
local inv_det = 1 / det
|
||||
return {
|
||||
{m[2][2] * inv_det, -m[1][2] * inv_det},
|
||||
{-m[2][1] * inv_det, m[1][1] * inv_det}
|
||||
}
|
||||
end,
|
||||
|
||||
rotation = function(angle)
|
||||
local cos, sin = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
{cos, -sin},
|
||||
{sin, cos}
|
||||
}
|
||||
end,
|
||||
|
||||
transform = function(m, v)
|
||||
return {
|
||||
x = m[1][1] * v.x + m[1][2] * v.y,
|
||||
y = m[2][1] * v.x + m[2][2] * v.y
|
||||
}
|
||||
end,
|
||||
|
||||
scale = function(sx, sy)
|
||||
sy = sy or sx
|
||||
return {
|
||||
{sx, 0},
|
||||
{0, sy}
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
math.mat3 = {
|
||||
identity = function()
|
||||
return {
|
||||
{1, 0, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
transform = function(x, y, angle, sx, sy)
|
||||
sx = sx or 1
|
||||
sy = sy or sx
|
||||
local cos, sin = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
{cos * sx, -sin * sy, x},
|
||||
{sin * sx, cos * sy, y},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
mul = function(a, b)
|
||||
local result = {
|
||||
{0, 0, 0},
|
||||
{0, 0, 0},
|
||||
{0, 0, 0}
|
||||
}
|
||||
for i = 1, 3 do
|
||||
for j = 1, 3 do
|
||||
for k = 1, 3 do
|
||||
result[i][j] = result[i][j] + a[i][k] * b[k][j]
|
||||
end
|
||||
end
|
||||
end
|
||||
return result
|
||||
end,
|
||||
|
||||
transform_point = function(m, v)
|
||||
local x = m[1][1] * v.x + m[1][2] * v.y + m[1][3]
|
||||
local y = m[2][1] * v.x + m[2][2] * v.y + m[2][3]
|
||||
local w = m[3][1] * v.x + m[3][2] * v.y + m[3][3]
|
||||
if math.abs(w) < 1e-10 then
|
||||
return {x = 0, y = 0}
|
||||
end
|
||||
return {x = x / w, y = y / w}
|
||||
end,
|
||||
|
||||
translation = function(x, y)
|
||||
return {
|
||||
{1, 0, x},
|
||||
{0, 1, y},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
rotation = function(angle)
|
||||
local cos, sin = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
{cos, -sin, 0},
|
||||
{sin, cos, 0},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
scale = function(sx, sy)
|
||||
sy = sy or sx
|
||||
return {
|
||||
{sx, 0, 0},
|
||||
{0, sy, 0},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
det = function(m)
|
||||
return m[1][1] * (m[2][2] * m[3][3] - m[2][3] * m[3][2]) -
|
||||
m[1][2] * (m[2][1] * m[3][3] - m[2][3] * m[3][1]) +
|
||||
m[1][3] * (m[2][1] * m[3][2] - m[2][2] * m[3][1])
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- GEOMETRY FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
math.geometry = {
|
||||
point_line_distance = function(px, py, x1, y1, x2, y2)
|
||||
local dx, dy = x2 - x1, y2 - y1
|
||||
local len_sq = dx * dx + dy * dy
|
||||
if len_sq < 1e-10 then
|
||||
return math.distance(px, py, x1, y1)
|
||||
end
|
||||
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
||||
t = math.clamp(t, 0, 1)
|
||||
local nearestX = x1 + t * dx
|
||||
local nearestY = y1 + t * dy
|
||||
return math.distance(px, py, nearestX, nearestY)
|
||||
end,
|
||||
|
||||
point_in_polygon = function(px, py, vertices)
|
||||
local inside = false
|
||||
local n = #vertices / 2
|
||||
for i = 1, n do
|
||||
local x1, y1 = vertices[i*2-1], vertices[i*2]
|
||||
local x2, y2
|
||||
if i == n then
|
||||
x2, y2 = vertices[1], vertices[2]
|
||||
else
|
||||
x2, y2 = vertices[i*2+1], vertices[i*2+2]
|
||||
end
|
||||
if ((y1 > py) ~= (y2 > py)) and
|
||||
(px < (x2 - x1) * (py - y1) / (y2 - y1) + x1) then
|
||||
inside = not inside
|
||||
end
|
||||
end
|
||||
return inside
|
||||
end,
|
||||
|
||||
triangle_area = function(x1, y1, x2, y2, x3, y3)
|
||||
return math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2)
|
||||
end,
|
||||
|
||||
point_in_triangle = function(px, py, x1, y1, x2, y2, x3, y3)
|
||||
local area = math.geometry.triangle_area(x1, y1, x2, y2, x3, y3)
|
||||
local area1 = math.geometry.triangle_area(px, py, x2, y2, x3, y3)
|
||||
local area2 = math.geometry.triangle_area(x1, y1, px, py, x3, y3)
|
||||
local area3 = math.geometry.triangle_area(x1, y1, x2, y2, px, py)
|
||||
return math.abs(area - (area1 + area2 + area3)) < 1e-10
|
||||
end,
|
||||
|
||||
line_intersect = function(x1, y1, x2, y2, x3, y3, x4, y4)
|
||||
local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
||||
if math.abs(d) < 1e-10 then
|
||||
return false, nil, nil
|
||||
end
|
||||
local ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d
|
||||
local ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d
|
||||
if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then
|
||||
local x = x1 + ua * (x2 - x1)
|
||||
local y = y1 + ua * (y2 - y1)
|
||||
return true, x, y
|
||||
end
|
||||
return false, nil, nil
|
||||
end,
|
||||
|
||||
closest_point_on_segment = function(px, py, x1, y1, x2, y2)
|
||||
local dx, dy = x2 - x1, y2 - y1
|
||||
local len_sq = dx * dx + dy * dy
|
||||
if len_sq < 1e-10 then
|
||||
return x1, y1
|
||||
end
|
||||
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
||||
t = math.clamp(t, 0, 1)
|
||||
return x1 + t * dx, y1 + t * dy
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- INTERPOLATION FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
math.interpolation = {
|
||||
bezier = function(t, p0, p1, p2, p3)
|
||||
t = math.clamp(t, 0, 1)
|
||||
local t2 = t * t
|
||||
local t3 = t2 * t
|
||||
local mt = 1 - t
|
||||
local mt2 = mt * mt
|
||||
local mt3 = mt2 * mt
|
||||
return p0 * mt3 + 3 * p1 * mt2 * t + 3 * p2 * mt * t2 + p3 * t3
|
||||
end,
|
||||
|
||||
catmull_rom = function(t, p0, p1, p2, p3)
|
||||
t = math.clamp(t, 0, 1)
|
||||
local t2 = t * t
|
||||
local t3 = t2 * t
|
||||
return 0.5 * (
|
||||
(2 * p1) +
|
||||
(-p0 + p2) * t +
|
||||
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
|
||||
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
|
||||
)
|
||||
end,
|
||||
|
||||
hermite = function(t, p0, p1, m0, m1)
|
||||
t = math.clamp(t, 0, 1)
|
||||
local t2 = t * t
|
||||
local t3 = t2 * t
|
||||
local h00 = 2 * t3 - 3 * t2 + 1
|
||||
local h10 = t3 - 2 * t2 + t
|
||||
local h01 = -2 * t3 + 3 * t2
|
||||
local h11 = t3 - t2
|
||||
return h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1
|
||||
end,
|
||||
|
||||
quadratic_bezier = function(t, p0, p1, p2)
|
||||
t = math.clamp(t, 0, 1)
|
||||
local mt = 1 - t
|
||||
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2
|
||||
end,
|
||||
|
||||
step = function(t, edge, x)
|
||||
return t < edge and 0 or x
|
||||
end,
|
||||
|
||||
smootherstep = function(edge0, edge1, x)
|
||||
local t = math.clamp((x - edge0) / (edge1 - edge0), 0, 1)
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
end
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package math
|
||||
|
||||
import luajit "git.sharkk.net/Sky/LuaJIT-to-Go"
|
||||
|
||||
func GetFunctionList() map[string]luajit.GoFunction {
|
||||
return map[string]luajit.GoFunction{
|
||||
"math_factorial": math_factorial,
|
||||
"math_gcd": math_gcd,
|
||||
"math_lcm": math_lcm,
|
||||
}
|
||||
}
|
||||
|
||||
func math_factorial(s *luajit.State) int {
|
||||
n := s.ToNumber(1)
|
||||
if n < 0 || n != float64(int(n)) || n > 170 {
|
||||
s.PushNil()
|
||||
s.PushString("invalid argument")
|
||||
return 2
|
||||
}
|
||||
|
||||
result := 1.0
|
||||
for i := 2; i <= int(n); i++ {
|
||||
result *= float64(i)
|
||||
}
|
||||
|
||||
s.PushNumber(result)
|
||||
return 1
|
||||
}
|
||||
|
||||
func math_gcd(s *luajit.State) int {
|
||||
a := int(s.ToNumber(1))
|
||||
b := int(s.ToNumber(2))
|
||||
|
||||
for b != 0 {
|
||||
a, b = b, a%b
|
||||
}
|
||||
|
||||
s.PushNumber(float64(a))
|
||||
return 1
|
||||
}
|
||||
|
||||
func math_lcm(s *luajit.State) int {
|
||||
a := int(s.ToNumber(1))
|
||||
b := int(s.ToNumber(2))
|
||||
|
||||
// Calculate GCD
|
||||
gcd := func(x, y int) int {
|
||||
for y != 0 {
|
||||
x, y = y, x%y
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
result := a * b / gcd(a, b)
|
||||
s.PushNumber(float64(result))
|
||||
return 1
|
||||
}
|
@ -1,817 +0,0 @@
|
||||
-- math.lua - Extended math library with advanced functions and utilities
|
||||
|
||||
local math_ext = {}
|
||||
|
||||
-- Import standard math functions to maintain compatibility
|
||||
for name, func in pairs(_G.math) do
|
||||
math_ext[name] = func
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- CONSTANTS (higher precision than standard library)
|
||||
-- ======================================================================
|
||||
|
||||
math_ext.pi = 3.14159265358979323846
|
||||
math_ext.tau = 6.28318530717958647693 -- 2*pi, useful for full rotations
|
||||
math_ext.e = 2.71828182845904523536
|
||||
math_ext.phi = 1.61803398874989484820 -- Golden ratio (1 + sqrt(5)) / 2
|
||||
math_ext.sqrt2 = 1.41421356237309504880
|
||||
math_ext.sqrt3 = 1.73205080756887729353
|
||||
math_ext.ln2 = 0.69314718055994530942 -- Natural log of 2
|
||||
math_ext.ln10 = 2.30258509299404568402 -- Natural log of 10
|
||||
math_ext.infinity = 1/0
|
||||
math_ext.nan = 0/0
|
||||
|
||||
-- ======================================================================
|
||||
-- EXTENDED FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
-- Cube root that handles negative numbers correctly (unlike x^(1/3))
|
||||
function math_ext.cbrt(x)
|
||||
return x < 0 and -(-x)^(1/3) or x^(1/3)
|
||||
end
|
||||
|
||||
-- Euclidean distance (hypotenuse) - more accurate than sqrt(x*x + y*y) for edge cases
|
||||
function math_ext.hypot(x, y)
|
||||
return math.sqrt(x * x + y * y)
|
||||
end
|
||||
|
||||
-- IEEE 754 NaN check - only reliable way to test for NaN
|
||||
function math_ext.isnan(x)
|
||||
return x ~= x
|
||||
end
|
||||
|
||||
-- Check if number is finite (not infinity or NaN)
|
||||
function math_ext.isfinite(x)
|
||||
return x > -math_ext.infinity and x < math_ext.infinity
|
||||
end
|
||||
|
||||
-- Mathematical sign function returning -1, 0, or 1
|
||||
function math_ext.sign(x)
|
||||
return x > 0 and 1 or (x < 0 and -1 or 0)
|
||||
end
|
||||
|
||||
-- Constrain value to range [min, max] - essential for safe calculations
|
||||
function math_ext.clamp(x, min, max)
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
-- Linear interpolation - fundamental for animation and gradients
|
||||
function math_ext.lerp(a, b, t)
|
||||
return a + (b - a) * t
|
||||
end
|
||||
|
||||
-- Smooth interpolation using Hermite polynomial - gives eased motion
|
||||
function math_ext.smoothstep(a, b, t)
|
||||
t = math_ext.clamp((t - a) / (b - a), 0, 1)
|
||||
return t * t * (3 - 2 * t)
|
||||
end
|
||||
|
||||
-- Map value from input range to output range - useful for scaling
|
||||
function math_ext.map(x, in_min, in_max, out_min, out_max)
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
|
||||
end
|
||||
|
||||
-- Round to nearest integer (more predictable than math.floor(x + 0.5))
|
||||
function math_ext.round(x)
|
||||
return x >= 0 and math.floor(x + 0.5) or math.ceil(x - 0.5)
|
||||
end
|
||||
|
||||
-- Round to specified decimal places using multiplication/division
|
||||
function math_ext.roundto(x, decimals)
|
||||
local mult = 10 ^ (decimals or 0)
|
||||
return math.floor(x * mult + 0.5) / mult
|
||||
end
|
||||
|
||||
-- Normalize angle to [-π, π] range for consistent angle calculations
|
||||
function math_ext.normalize_angle(angle)
|
||||
return angle - 2 * math_ext.pi * math.floor((angle + math_ext.pi) / (2 * math_ext.pi))
|
||||
end
|
||||
|
||||
-- 2D Euclidean distance between two points
|
||||
function math_ext.distance(x1, y1, x2, y2)
|
||||
local dx, dy = x2 - x1, y2 - y1
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- RANDOM NUMBER FUNCTIONS - Enhanced random utilities
|
||||
-- ======================================================================
|
||||
|
||||
-- Random float in specified range [min, max) - more flexible than math.random()
|
||||
function math_ext.randomf(min, max)
|
||||
if not min and not max then
|
||||
return math.random()
|
||||
elseif not max then
|
||||
max = min
|
||||
min = 0
|
||||
end
|
||||
return min + math.random() * (max - min)
|
||||
end
|
||||
|
||||
-- Random integer in range [min, max] inclusive - handles single argument case
|
||||
function math_ext.randint(min, max)
|
||||
if not max then
|
||||
max = min
|
||||
min = 1
|
||||
end
|
||||
return math.floor(math.random() * (max - min + 1) + min)
|
||||
end
|
||||
|
||||
-- Random boolean with configurable probability - useful for procedural generation
|
||||
function math_ext.randboolean(p)
|
||||
p = p or 0.5
|
||||
return math.random() < p
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- STATISTICS FUNCTIONS - Robust statistical calculations
|
||||
-- ======================================================================
|
||||
|
||||
-- Sum of numeric values in table - filters out non-numbers automatically
|
||||
function math_ext.sum(t)
|
||||
if type(t) ~= "table" then return 0 end
|
||||
|
||||
local sum = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
sum = sum + t[i]
|
||||
end
|
||||
end
|
||||
return sum
|
||||
end
|
||||
|
||||
-- Arithmetic mean (average) - handles mixed-type arrays safely
|
||||
function math_ext.mean(t)
|
||||
if type(t) ~= "table" or #t == 0 then return 0 end
|
||||
|
||||
local sum = 0
|
||||
local count = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
sum = sum + t[i]
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return count > 0 and sum / count or 0
|
||||
end
|
||||
|
||||
-- Median value - sorts data to find middle value (handles even/odd lengths)
|
||||
function math_ext.median(t)
|
||||
if type(t) ~= "table" or #t == 0 then return 0 end
|
||||
|
||||
local nums = {}
|
||||
local count = 0
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
count = count + 1
|
||||
nums[count] = t[i]
|
||||
end
|
||||
end
|
||||
|
||||
if count == 0 then return 0 end
|
||||
|
||||
table.sort(nums)
|
||||
|
||||
if count % 2 == 0 then
|
||||
return (nums[count/2] + nums[count/2 + 1]) / 2
|
||||
else
|
||||
return nums[math.ceil(count/2)]
|
||||
end
|
||||
end
|
||||
|
||||
-- Sample variance - measures spread using n-1 denominator (Bessel's correction)
|
||||
function math_ext.variance(t)
|
||||
if type(t) ~= "table" then return 0 end
|
||||
|
||||
local count = 0
|
||||
local m = math_ext.mean(t)
|
||||
local sum = 0
|
||||
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
local dev = t[i] - m
|
||||
sum = sum + dev * dev
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
return count > 1 and sum / count or 0
|
||||
end
|
||||
|
||||
-- Sample standard deviation - square root of variance
|
||||
function math_ext.stdev(t)
|
||||
return math.sqrt(math_ext.variance(t))
|
||||
end
|
||||
|
||||
-- Population variance - uses n denominator instead of n-1
|
||||
function math_ext.pvariance(t)
|
||||
if type(t) ~= "table" then return 0 end
|
||||
|
||||
local count = 0
|
||||
local m = math_ext.mean(t)
|
||||
local sum = 0
|
||||
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
local dev = t[i] - m
|
||||
sum = sum + dev * dev
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
|
||||
return count > 0 and sum / count or 0
|
||||
end
|
||||
|
||||
-- Population standard deviation
|
||||
function math_ext.pstdev(t)
|
||||
return math.sqrt(math_ext.pvariance(t))
|
||||
end
|
||||
|
||||
-- Mode - most frequently occurring value (first encountered if tie)
|
||||
function math_ext.mode(t)
|
||||
if type(t) ~= "table" or #t == 0 then return nil end
|
||||
|
||||
local counts = {}
|
||||
local most_frequent = nil
|
||||
local max_count = 0
|
||||
|
||||
for i=1, #t do
|
||||
local v = t[i]
|
||||
counts[v] = (counts[v] or 0) + 1
|
||||
if counts[v] > max_count then
|
||||
max_count = counts[v]
|
||||
most_frequent = v
|
||||
end
|
||||
end
|
||||
|
||||
return most_frequent
|
||||
end
|
||||
|
||||
-- Simultaneous min/max - more efficient than separate calls
|
||||
function math_ext.minmax(t)
|
||||
if type(t) ~= "table" or #t == 0 then return nil, nil end
|
||||
|
||||
local min, max
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
min = t[i]
|
||||
max = t[i]
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if min == nil then return nil, nil end
|
||||
|
||||
for i=1, #t do
|
||||
if type(t[i]) == "number" then
|
||||
if t[i] < min then min = t[i] end
|
||||
if t[i] > max then max = t[i] end
|
||||
end
|
||||
end
|
||||
|
||||
return min, max
|
||||
end
|
||||
|
||||
-- ======================================================================
|
||||
-- VECTOR OPERATIONS - 2D/3D vector math for graphics and physics
|
||||
-- ======================================================================
|
||||
|
||||
-- 2D Vector operations - fundamental for 2D graphics, physics, and UI
|
||||
math_ext.vec2 = {
|
||||
-- Create new 2D vector with default zero values
|
||||
new = function(x, y)
|
||||
return {x = x or 0, y = y or 0}
|
||||
end,
|
||||
|
||||
-- Create independent copy to avoid reference issues
|
||||
copy = function(v)
|
||||
return {x = v.x, y = v.y}
|
||||
end,
|
||||
|
||||
-- Vector addition - combines two displacement vectors
|
||||
add = function(a, b)
|
||||
return {x = a.x + b.x, y = a.y + b.y}
|
||||
end,
|
||||
|
||||
-- Vector subtraction - difference between two points/vectors
|
||||
sub = function(a, b)
|
||||
return {x = a.x - b.x, y = a.y - b.y}
|
||||
end,
|
||||
|
||||
-- Scalar or component-wise multiplication
|
||||
mul = function(a, b)
|
||||
if type(b) == "number" then
|
||||
return {x = a.x * b, y = a.y * b}
|
||||
end
|
||||
return {x = a.x * b.x, y = a.y * b.y}
|
||||
end,
|
||||
|
||||
-- Scalar or component-wise division (uses multiplication for efficiency)
|
||||
div = function(a, b)
|
||||
if type(b) == "number" then
|
||||
local inv = 1 / b
|
||||
return {x = a.x * inv, y = a.y * inv}
|
||||
end
|
||||
return {x = a.x / b.x, y = a.y / b.y}
|
||||
end,
|
||||
|
||||
-- Dot product - measures vector similarity/projection
|
||||
dot = function(a, b)
|
||||
return a.x * b.x + a.y * b.y
|
||||
end,
|
||||
|
||||
-- Euclidean length/magnitude of vector
|
||||
length = function(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y)
|
||||
end,
|
||||
|
||||
-- Squared length - avoids sqrt for performance when comparing lengths
|
||||
length_squared = function(v)
|
||||
return v.x * v.x + v.y * v.y
|
||||
end,
|
||||
|
||||
-- Distance between two points
|
||||
distance = function(a, b)
|
||||
local dx, dy = b.x - a.x, b.y - a.y
|
||||
return math.sqrt(dx * dx + dy * dy)
|
||||
end,
|
||||
|
||||
-- Squared distance - avoids sqrt for performance
|
||||
distance_squared = function(a, b)
|
||||
local dx, dy = b.x - a.x, b.y - a.y
|
||||
return dx * dx + dy * dy
|
||||
end,
|
||||
|
||||
-- Convert to unit vector (length 1) - preserves direction
|
||||
normalize = function(v)
|
||||
local len = math.sqrt(v.x * v.x + v.y * v.y)
|
||||
if len > 1e-10 then
|
||||
local inv_len = 1 / len
|
||||
return {x = v.x * inv_len, y = v.y * inv_len}
|
||||
end
|
||||
return {x = 0, y = 0}
|
||||
end,
|
||||
|
||||
-- Rotate vector by angle (counterclockwise)
|
||||
rotate = function(v, angle)
|
||||
local c, s = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
x = v.x * c - v.y * s,
|
||||
y = v.x * s + v.y * c
|
||||
}
|
||||
end,
|
||||
|
||||
-- Get angle of vector from positive x-axis
|
||||
angle = function(v)
|
||||
return math.atan2(v.y, v.x)
|
||||
end,
|
||||
|
||||
-- Linear interpolation between two vectors
|
||||
lerp = function(a, b, t)
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
return {
|
||||
x = a.x + (b.x - a.x) * t,
|
||||
y = a.y + (b.y - a.y) * t
|
||||
}
|
||||
end,
|
||||
|
||||
-- Reflect vector across normal (like light bouncing off surface)
|
||||
reflect = function(v, normal)
|
||||
local dot = v.x * normal.x + v.y * normal.y
|
||||
return {
|
||||
x = v.x - 2 * dot * normal.x,
|
||||
y = v.y - 2 * dot * normal.y
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
-- 3D Vector operations - essential for 3D graphics and spatial calculations
|
||||
math_ext.vec3 = {
|
||||
new = function(x, y, z)
|
||||
return {x = x or 0, y = y or 0, z = z or 0}
|
||||
end,
|
||||
|
||||
copy = function(v)
|
||||
return {x = v.x, y = v.y, z = v.z}
|
||||
end,
|
||||
|
||||
add = function(a, b)
|
||||
return {x = a.x + b.x, y = a.y + b.y, z = a.z + b.z}
|
||||
end,
|
||||
|
||||
sub = function(a, b)
|
||||
return {x = a.x - b.x, y = a.y - b.y, z = a.z - b.z}
|
||||
end,
|
||||
|
||||
mul = function(a, b)
|
||||
if type(b) == "number" then
|
||||
return {x = a.x * b, y = a.y * b, z = a.z * b}
|
||||
end
|
||||
return {x = a.x * b.x, y = a.y * b.y, z = a.z * b.z}
|
||||
end,
|
||||
|
||||
div = function(a, b)
|
||||
if type(b) == "number" then
|
||||
local inv = 1 / b
|
||||
return {x = a.x * inv, y = a.y * inv, z = a.z * inv}
|
||||
end
|
||||
return {x = a.x / b.x, y = a.y / b.y, z = a.z / b.z}
|
||||
end,
|
||||
|
||||
dot = function(a, b)
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z
|
||||
end,
|
||||
|
||||
-- Cross product - creates perpendicular vector (right-hand rule)
|
||||
cross = function(a, b)
|
||||
return {
|
||||
x = a.y * b.z - a.z * b.y,
|
||||
y = a.z * b.x - a.x * b.z,
|
||||
z = a.x * b.y - a.y * b.x
|
||||
}
|
||||
end,
|
||||
|
||||
length = function(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||
end,
|
||||
|
||||
length_squared = function(v)
|
||||
return v.x * v.x + v.y * v.y + v.z * v.z
|
||||
end,
|
||||
|
||||
distance = function(a, b)
|
||||
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
||||
return math.sqrt(dx * dx + dy * dy + dz * dz)
|
||||
end,
|
||||
|
||||
distance_squared = function(a, b)
|
||||
local dx, dy, dz = b.x - a.x, b.y - a.y, b.z - a.z
|
||||
return dx * dx + dy * dy + dz * dz
|
||||
end,
|
||||
|
||||
normalize = function(v)
|
||||
local len = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
|
||||
if len > 1e-10 then
|
||||
local inv_len = 1 / len
|
||||
return {x = v.x * inv_len, y = v.y * inv_len, z = v.z * inv_len}
|
||||
end
|
||||
return {x = 0, y = 0, z = 0}
|
||||
end,
|
||||
|
||||
lerp = function(a, b, t)
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
return {
|
||||
x = a.x + (b.x - a.x) * t,
|
||||
y = a.y + (b.y - a.y) * t,
|
||||
z = a.z + (b.z - a.z) * t
|
||||
}
|
||||
end,
|
||||
|
||||
reflect = function(v, normal)
|
||||
local dot = v.x * normal.x + v.y * normal.y + v.z * normal.z
|
||||
return {
|
||||
x = v.x - 2 * dot * normal.x,
|
||||
y = v.y - 2 * dot * normal.y,
|
||||
z = v.z - 2 * dot * normal.z
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- MATRIX OPERATIONS - Linear transformations for graphics and math
|
||||
-- ======================================================================
|
||||
|
||||
math_ext.mat2 = {
|
||||
-- Create 2x2 matrix with specified values (defaults to identity)
|
||||
new = function(a, b, c, d)
|
||||
return {
|
||||
{a or 1, b or 0},
|
||||
{c or 0, d or 1}
|
||||
}
|
||||
end,
|
||||
|
||||
-- 2x2 identity matrix - no transformation
|
||||
identity = function()
|
||||
return {{1, 0}, {0, 1}}
|
||||
end,
|
||||
|
||||
-- Matrix multiplication - combines transformations (order matters)
|
||||
mul = function(a, b)
|
||||
return {
|
||||
{
|
||||
a[1][1] * b[1][1] + a[1][2] * b[2][1],
|
||||
a[1][1] * b[1][2] + a[1][2] * b[2][2]
|
||||
},
|
||||
{
|
||||
a[2][1] * b[1][1] + a[2][2] * b[2][1],
|
||||
a[2][1] * b[1][2] + a[2][2] * b[2][2]
|
||||
}
|
||||
}
|
||||
end,
|
||||
|
||||
-- Determinant - measures area scaling factor
|
||||
det = function(m)
|
||||
return m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
||||
end,
|
||||
|
||||
-- Inverse matrix - reverses transformation (if possible)
|
||||
inverse = function(m)
|
||||
local det = m[1][1] * m[2][2] - m[1][2] * m[2][1]
|
||||
if math.abs(det) < 1e-10 then
|
||||
return nil -- Matrix is not invertible
|
||||
end
|
||||
|
||||
local inv_det = 1 / det
|
||||
return {
|
||||
{m[2][2] * inv_det, -m[1][2] * inv_det},
|
||||
{-m[2][1] * inv_det, m[1][1] * inv_det}
|
||||
}
|
||||
end,
|
||||
|
||||
-- Create rotation matrix for given angle
|
||||
rotation = function(angle)
|
||||
local cos, sin = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
{cos, -sin},
|
||||
{sin, cos}
|
||||
}
|
||||
end,
|
||||
|
||||
-- Apply matrix transformation to 2D vector
|
||||
transform = function(m, v)
|
||||
return {
|
||||
x = m[1][1] * v.x + m[1][2] * v.y,
|
||||
y = m[2][1] * v.x + m[2][2] * v.y
|
||||
}
|
||||
end,
|
||||
|
||||
-- Create scaling matrix (uniform if sy omitted)
|
||||
scale = function(sx, sy)
|
||||
sy = sy or sx
|
||||
return {
|
||||
{sx, 0},
|
||||
{0, sy}
|
||||
}
|
||||
end
|
||||
}
|
||||
|
||||
math_ext.mat3 = {
|
||||
-- 3x3 identity matrix - useful for 2D transformations with translation
|
||||
identity = function()
|
||||
return {
|
||||
{1, 0, 0},
|
||||
{0, 1, 0},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
-- Complete 2D transformation matrix (translate, rotate, scale)
|
||||
transform = function(x, y, angle, sx, sy)
|
||||
sx = sx or 1
|
||||
sy = sy or sx
|
||||
local cos, sin = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
{cos * sx, -sin * sy, x},
|
||||
{sin * sx, cos * sy, y},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
-- 3x3 matrix multiplication - combines transformations
|
||||
mul = function(a, b)
|
||||
local result = {
|
||||
{0, 0, 0},
|
||||
{0, 0, 0},
|
||||
{0, 0, 0}
|
||||
}
|
||||
|
||||
for i = 1, 3 do
|
||||
for j = 1, 3 do
|
||||
for k = 1, 3 do
|
||||
result[i][j] = result[i][j] + a[i][k] * b[k][j]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return result
|
||||
end,
|
||||
|
||||
-- Transform 2D point using homogeneous coordinates
|
||||
transform_point = function(m, v)
|
||||
local x = m[1][1] * v.x + m[1][2] * v.y + m[1][3]
|
||||
local y = m[2][1] * v.x + m[2][2] * v.y + m[2][3]
|
||||
local w = m[3][1] * v.x + m[3][2] * v.y + m[3][3]
|
||||
|
||||
if math.abs(w) < 1e-10 then
|
||||
return {x = 0, y = 0}
|
||||
end
|
||||
|
||||
return {x = x / w, y = y / w}
|
||||
end,
|
||||
|
||||
-- Translation matrix
|
||||
translation = function(x, y)
|
||||
return {
|
||||
{1, 0, x},
|
||||
{0, 1, y},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
-- 2D rotation matrix in 3x3 form
|
||||
rotation = function(angle)
|
||||
local cos, sin = math.cos(angle), math.sin(angle)
|
||||
return {
|
||||
{cos, -sin, 0},
|
||||
{sin, cos, 0},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
-- Scaling matrix in 3x3 form
|
||||
scale = function(sx, sy)
|
||||
sy = sy or sx
|
||||
return {
|
||||
{sx, 0, 0},
|
||||
{0, sy, 0},
|
||||
{0, 0, 1}
|
||||
}
|
||||
end,
|
||||
|
||||
-- 3x3 determinant - measures volume scaling
|
||||
det = function(m)
|
||||
return m[1][1] * (m[2][2] * m[3][3] - m[2][3] * m[3][2]) -
|
||||
m[1][2] * (m[2][1] * m[3][3] - m[2][3] * m[3][1]) +
|
||||
m[1][3] * (m[2][1] * m[3][2] - m[2][2] * m[3][1])
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- GEOMETRY FUNCTIONS - Computational geometry utilities
|
||||
-- ======================================================================
|
||||
|
||||
math_ext.geometry = {
|
||||
-- Shortest distance from point to line segment
|
||||
point_line_distance = function(px, py, x1, y1, x2, y2)
|
||||
local dx, dy = x2 - x1, y2 - y1
|
||||
local len_sq = dx * dx + dy * dy
|
||||
|
||||
if len_sq < 1e-10 then
|
||||
return math_ext.distance(px, py, x1, y1)
|
||||
end
|
||||
|
||||
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
|
||||
local nearestX = x1 + t * dx
|
||||
local nearestY = y1 + t * dy
|
||||
|
||||
return math_ext.distance(px, py, nearestX, nearestY)
|
||||
end,
|
||||
|
||||
-- Point-in-polygon test using ray casting algorithm
|
||||
point_in_polygon = function(px, py, vertices)
|
||||
local inside = false
|
||||
local n = #vertices / 2
|
||||
|
||||
for i = 1, n do
|
||||
local x1, y1 = vertices[i*2-1], vertices[i*2]
|
||||
local x2, y2
|
||||
|
||||
if i == n then
|
||||
x2, y2 = vertices[1], vertices[2]
|
||||
else
|
||||
x2, y2 = vertices[i*2+1], vertices[i*2+2]
|
||||
end
|
||||
|
||||
if ((y1 > py) ~= (y2 > py)) and
|
||||
(px < (x2 - x1) * (py - y1) / (y2 - y1) + x1) then
|
||||
inside = not inside
|
||||
end
|
||||
end
|
||||
|
||||
return inside
|
||||
end,
|
||||
|
||||
-- Calculate triangle area using cross product method
|
||||
triangle_area = function(x1, y1, x2, y2, x3, y3)
|
||||
return math.abs((x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2)
|
||||
end,
|
||||
|
||||
-- Point-in-triangle test using barycentric coordinates
|
||||
point_in_triangle = function(px, py, x1, y1, x2, y2, x3, y3)
|
||||
local area = math_ext.geometry.triangle_area(x1, y1, x2, y2, x3, y3)
|
||||
local area1 = math_ext.geometry.triangle_area(px, py, x2, y2, x3, y3)
|
||||
local area2 = math_ext.geometry.triangle_area(x1, y1, px, py, x3, y3)
|
||||
local area3 = math_ext.geometry.triangle_area(x1, y1, x2, y2, px, py)
|
||||
|
||||
return math.abs(area - (area1 + area2 + area3)) < 1e-10
|
||||
end,
|
||||
|
||||
-- Line segment intersection using parametric equations
|
||||
line_intersect = function(x1, y1, x2, y2, x3, y3, x4, y4)
|
||||
local d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
||||
|
||||
if math.abs(d) < 1e-10 then
|
||||
return false, nil, nil -- Lines are parallel
|
||||
end
|
||||
|
||||
local ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / d
|
||||
local ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / d
|
||||
|
||||
if ua >= 0 and ua <= 1 and ub >= 0 and ub <= 1 then
|
||||
local x = x1 + ua * (x2 - x1)
|
||||
local y = y1 + ua * (y2 - y1)
|
||||
return true, x, y
|
||||
end
|
||||
|
||||
return false, nil, nil
|
||||
end,
|
||||
|
||||
-- Find closest point on line segment to given point
|
||||
closest_point_on_segment = function(px, py, x1, y1, x2, y2)
|
||||
local dx, dy = x2 - x1, y2 - y1
|
||||
local len_sq = dx * dx + dy * dy
|
||||
|
||||
if len_sq < 1e-10 then
|
||||
return x1, y1
|
||||
end
|
||||
|
||||
local t = ((px - x1) * dx + (py - y1) * dy) / len_sq
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
|
||||
return x1 + t * dx, y1 + t * dy
|
||||
end
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- INTERPOLATION FUNCTIONS - Smooth transitions and curve generation
|
||||
-- ======================================================================
|
||||
|
||||
math_ext.interpolation = {
|
||||
-- Cubic Bézier curve - standard for smooth animations
|
||||
bezier = function(t, p0, p1, p2, p3)
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
local t2 = t * t
|
||||
local t3 = t2 * t
|
||||
local mt = 1 - t
|
||||
local mt2 = mt * mt
|
||||
local mt3 = mt2 * mt
|
||||
|
||||
return p0 * mt3 + 3 * p1 * mt2 * t + 3 * p2 * mt * t2 + p3 * t3
|
||||
end,
|
||||
|
||||
-- Catmull-Rom spline - smooth interpolation through points
|
||||
catmull_rom = function(t, p0, p1, p2, p3)
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
local t2 = t * t
|
||||
local t3 = t2 * t
|
||||
|
||||
return 0.5 * (
|
||||
(2 * p1) +
|
||||
(-p0 + p2) * t +
|
||||
(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
|
||||
(-p0 + 3 * p1 - 3 * p2 + p3) * t3
|
||||
)
|
||||
end,
|
||||
|
||||
-- Hermite interpolation with tangent control
|
||||
hermite = function(t, p0, p1, m0, m1)
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
local t2 = t * t
|
||||
local t3 = t2 * t
|
||||
local h00 = 2 * t3 - 3 * t2 + 1
|
||||
local h10 = t3 - 2 * t2 + t
|
||||
local h01 = -2 * t3 + 3 * t2
|
||||
local h11 = t3 - t2
|
||||
|
||||
return h00 * p0 + h10 * m0 + h01 * p1 + h11 * m1
|
||||
end,
|
||||
|
||||
-- Quadratic Bézier - simpler curve with one control point
|
||||
quadratic_bezier = function(t, p0, p1, p2)
|
||||
t = math_ext.clamp(t, 0, 1)
|
||||
local mt = 1 - t
|
||||
return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2
|
||||
end,
|
||||
|
||||
-- Step function - instant transition at threshold
|
||||
step = function(t, edge, x)
|
||||
return t < edge and 0 or x
|
||||
end,
|
||||
|
||||
-- Smoothstep - S-curve interpolation (3rd order)
|
||||
smoothstep = function(edge0, edge1, x)
|
||||
local t = math_ext.clamp((x - edge0) / (edge1 - edge0), 0, 1)
|
||||
return t * t * (3 - 2 * t)
|
||||
end,
|
||||
|
||||
-- Smootherstep - Even smoother S-curve (5th order, Ken Perlin)
|
||||
smootherstep = function(edge0, edge1, x)
|
||||
local t = math_ext.clamp((x - edge0) / (edge1 - edge0), 0, 1)
|
||||
return t * t * t * (t * (t * 6 - 15) + 10)
|
||||
end
|
||||
}
|
||||
|
||||
return math_ext
|
@ -10,7 +10,6 @@ import (
|
||||
"Moonshark/modules/fs"
|
||||
"Moonshark/modules/http"
|
||||
"Moonshark/modules/kv"
|
||||
"Moonshark/modules/math"
|
||||
"Moonshark/modules/sql"
|
||||
lua_string "Moonshark/modules/string+"
|
||||
|
||||
@ -36,7 +35,6 @@ func New() *Registry {
|
||||
}
|
||||
|
||||
maps.Copy(r.goFuncs, lua_string.GetFunctionList())
|
||||
maps.Copy(r.goFuncs, math.GetFunctionList())
|
||||
maps.Copy(r.goFuncs, crypto.GetFunctionList())
|
||||
maps.Copy(r.goFuncs, fs.GetFunctionList())
|
||||
maps.Copy(r.goFuncs, http.GetFunctionList())
|
||||
|
448
tests/math.lua
448
tests/math.lua
@ -1,171 +1,393 @@
|
||||
require("tests")
|
||||
local math = require("math")
|
||||
|
||||
-- Test constants
|
||||
test("Constants", function()
|
||||
-- Constants tests
|
||||
test("math.pi", function()
|
||||
assert_close(math.pi, 3.14159265358979323846)
|
||||
assert_close(math.tau, 6.28318530717958647693)
|
||||
assert_close(math.e, 2.71828182845904523536)
|
||||
assert_close(math.phi, 1.61803398874989484820)
|
||||
assert_equal(math.infinity, 1/0)
|
||||
assert_equal(math.isnan(math.nan), true)
|
||||
assert(math.pi > 3.14 and math.pi < 3.15)
|
||||
end)
|
||||
|
||||
-- Test extended functions
|
||||
test("Extended Functions", function()
|
||||
test("math.tau", function()
|
||||
assert_close(math.tau, 6.28318530717958647693)
|
||||
assert_close(math.tau, 2 * math.pi)
|
||||
end)
|
||||
|
||||
test("math.e", function()
|
||||
assert_close(math.e, 2.71828182845904523536)
|
||||
assert(math.e > 2.7 and math.e < 2.8)
|
||||
end)
|
||||
|
||||
test("math.phi", function()
|
||||
assert_close(math.phi, 1.61803398874989484820)
|
||||
assert_close(math.phi, (1 + math.sqrt(5)) / 2)
|
||||
end)
|
||||
|
||||
test("math.infinity", function()
|
||||
assert_equal(math.infinity, 1/0)
|
||||
assert(math.infinity > 0)
|
||||
end)
|
||||
|
||||
test("math.nan", function()
|
||||
assert_equal(math.isnan(math.nan), true)
|
||||
assert(math.nan ~= math.nan) -- NaN property
|
||||
end)
|
||||
|
||||
-- Extended functions tests
|
||||
test("math.cbrt", function()
|
||||
assert_close(math.cbrt(8), 2)
|
||||
assert_close(math.cbrt(-8), -2)
|
||||
end)
|
||||
|
||||
test("math.hypot", function()
|
||||
assert_close(math.hypot(3, 4), 5)
|
||||
assert_close(math.hypot(5, 12), 13)
|
||||
end)
|
||||
|
||||
test("math.isnan", function()
|
||||
assert_equal(math.isnan(0/0), true)
|
||||
assert_equal(math.isnan(5), false)
|
||||
end)
|
||||
|
||||
test("math.isfinite", function()
|
||||
assert_equal(math.isfinite(5), true)
|
||||
assert_equal(math.isfinite(math.infinity), false)
|
||||
end)
|
||||
|
||||
test("math.sign", function()
|
||||
assert_equal(math.sign(5), 1)
|
||||
assert_equal(math.sign(-5), -1)
|
||||
assert_equal(math.sign(0), 0)
|
||||
end)
|
||||
|
||||
test("math.clamp", function()
|
||||
assert_equal(math.clamp(5, 0, 3), 3)
|
||||
assert_equal(math.clamp(-1, 0, 3), 0)
|
||||
assert_equal(math.clamp(2, 0, 3), 2)
|
||||
end)
|
||||
|
||||
test("math.lerp", function()
|
||||
assert_close(math.lerp(0, 10, 0.5), 5)
|
||||
assert_close(math.lerp(2, 8, 0.25), 3.5)
|
||||
end)
|
||||
|
||||
test("math.smoothstep", function()
|
||||
assert_close(math.smoothstep(0, 1, 0.5), 0.5)
|
||||
assert_close(math.smoothstep(0, 10, 5), 0.5)
|
||||
end)
|
||||
|
||||
test("math.map", function()
|
||||
assert_close(math.map(5, 0, 10, 0, 100), 50)
|
||||
assert_close(math.map(2, 0, 4, 10, 20), 15)
|
||||
end)
|
||||
|
||||
test("math.round", function()
|
||||
assert_equal(math.round(2.7), 3)
|
||||
assert_equal(math.round(-2.7), -3)
|
||||
assert_equal(math.round(2.3), 2)
|
||||
end)
|
||||
|
||||
test("math.roundto", function()
|
||||
assert_close(math.roundto(3.14159, 2), 3.14)
|
||||
assert_close(math.roundto(123.456, 1), 123.5)
|
||||
end)
|
||||
|
||||
test("math.normalize_angle", function()
|
||||
assert_close(math.normalize_angle(math.pi * 2.5), math.pi * 0.5)
|
||||
assert_close(math.normalize_angle(-math.pi * 2.5), -math.pi * 0.5)
|
||||
end)
|
||||
|
||||
test("math.distance", function()
|
||||
assert_close(math.distance(0, 0, 3, 4), 5)
|
||||
assert_close(math.distance(1, 1, 4, 5), 5)
|
||||
end)
|
||||
|
||||
-- Test random functions
|
||||
test("Random Functions", function()
|
||||
local r = math.randomf(0, 1)
|
||||
assert(r >= 0 and r < 1, "randomf should be in range [0, 1)")
|
||||
local i = math.randint(1, 10)
|
||||
assert(i >= 1 and i <= 10, "randint should be in range [1, 10]")
|
||||
assert_equal(type(math.randboolean()), "boolean")
|
||||
test("math.factorial", function()
|
||||
assert_equal(math.factorial(5), 120)
|
||||
assert_equal(math.factorial(0), 1)
|
||||
assert_equal(math.factorial(-1), nil)
|
||||
end)
|
||||
|
||||
-- Test statistics
|
||||
test("Statistics", function()
|
||||
local data = {1, 2, 3, 4, 5}
|
||||
assert_equal(math.sum(data), 15)
|
||||
assert_equal(math.mean(data), 3)
|
||||
assert_equal(math.median(data), 3)
|
||||
assert_close(math.variance(data), 2)
|
||||
assert_close(math.stdev(data), math.sqrt(2))
|
||||
local min, max = math.minmax(data)
|
||||
assert_equal(min, 1)
|
||||
assert_equal(max, 5)
|
||||
test("math.gcd", function()
|
||||
assert_equal(math.gcd(48, 18), 6)
|
||||
assert_equal(math.gcd(100, 25), 25)
|
||||
end)
|
||||
|
||||
test("math.lcm", function()
|
||||
assert_equal(math.lcm(4, 6), 12)
|
||||
assert_equal(math.lcm(15, 20), 60)
|
||||
end)
|
||||
|
||||
-- Random functions tests
|
||||
test("math.randomf", function()
|
||||
local r1 = math.randomf(0, 1)
|
||||
local r2 = math.randomf(5, 10)
|
||||
assert(r1 >= 0 and r1 < 1)
|
||||
assert(r2 >= 5 and r2 < 10)
|
||||
end)
|
||||
|
||||
test("math.randint", function()
|
||||
local i1 = math.randint(1, 10)
|
||||
local i2 = math.randint(50, 60)
|
||||
assert(i1 >= 1 and i1 <= 10)
|
||||
assert(i2 >= 50 and i2 <= 60)
|
||||
end)
|
||||
|
||||
test("math.randboolean", function()
|
||||
local b1 = math.randboolean()
|
||||
local b2 = math.randboolean(0.8)
|
||||
assert_equal(type(b1), "boolean")
|
||||
assert_equal(type(b2), "boolean")
|
||||
end)
|
||||
|
||||
-- Statistics tests
|
||||
test("math.sum", function()
|
||||
assert_equal(math.sum({1, 2, 3, 4, 5}), 15)
|
||||
assert_equal(math.sum({10, 20, 30}), 60)
|
||||
end)
|
||||
|
||||
test("math.mean", function()
|
||||
assert_equal(math.mean({1, 2, 3, 4, 5}), 3)
|
||||
assert_equal(math.mean({10, 20, 30}), 20)
|
||||
end)
|
||||
|
||||
test("math.median", function()
|
||||
assert_equal(math.median({1, 2, 3, 4, 5}), 3)
|
||||
assert_equal(math.median({1, 2, 3, 4}), 2.5)
|
||||
end)
|
||||
|
||||
test("math.variance", function()
|
||||
assert_close(math.variance({1, 2, 3, 4, 5}), 2)
|
||||
assert_close(math.variance({10, 10, 10}), 0)
|
||||
end)
|
||||
|
||||
test("math.stdev", function()
|
||||
assert_close(math.stdev({1, 2, 3, 4, 5}), math.sqrt(2))
|
||||
assert_close(math.stdev({10, 10, 10}), 0)
|
||||
end)
|
||||
|
||||
test("math.mode", function()
|
||||
assert_equal(math.mode({1, 2, 2, 3}), 2)
|
||||
assert_equal(math.mode({5, 5, 4, 4, 4}), 4)
|
||||
end)
|
||||
|
||||
-- Test 2D vectors
|
||||
test("2D Vectors", function()
|
||||
test("math.minmax", function()
|
||||
local min1, max1 = math.minmax({1, 2, 3, 4, 5})
|
||||
local min2, max2 = math.minmax({-5, 0, 10})
|
||||
assert_equal(min1, 1)
|
||||
assert_equal(max1, 5)
|
||||
assert_equal(min2, -5)
|
||||
assert_equal(max2, 10)
|
||||
end)
|
||||
|
||||
-- 2D Vector tests
|
||||
test("math.vec2.new", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local v2 = math.vec2.new(1, 2)
|
||||
|
||||
local v2 = math.vec2.new()
|
||||
assert_equal(v1.x, 3)
|
||||
assert_equal(v1.y, 4)
|
||||
assert_close(math.vec2.length(v1), 5)
|
||||
assert_equal(math.vec2.length_squared(v1), 25)
|
||||
|
||||
local v3 = math.vec2.add(v1, v2)
|
||||
assert_equal(v3.x, 4)
|
||||
assert_equal(v3.y, 6)
|
||||
|
||||
local v4 = math.vec2.sub(v1, v2)
|
||||
assert_equal(v4.x, 2)
|
||||
assert_equal(v4.y, 2)
|
||||
|
||||
local v5 = math.vec2.mul(v1, 2)
|
||||
assert_equal(v5.x, 6)
|
||||
assert_equal(v5.y, 8)
|
||||
|
||||
assert_equal(v2.x, 0)
|
||||
assert_equal(v2.y, 0)
|
||||
end)
|
||||
|
||||
test("math.vec2.add", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local v2 = math.vec2.new(1, 2)
|
||||
local result = math.vec2.add(v1, v2)
|
||||
assert_equal(result.x, 4)
|
||||
assert_equal(result.y, 6)
|
||||
end)
|
||||
|
||||
test("math.vec2.sub", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local v2 = math.vec2.new(1, 2)
|
||||
local result = math.vec2.sub(v1, v2)
|
||||
assert_equal(result.x, 2)
|
||||
assert_equal(result.y, 2)
|
||||
end)
|
||||
|
||||
test("math.vec2.mul", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local result1 = math.vec2.mul(v1, 2)
|
||||
local result2 = math.vec2.mul(v1, math.vec2.new(2, 3))
|
||||
assert_equal(result1.x, 6)
|
||||
assert_equal(result1.y, 8)
|
||||
assert_equal(result2.x, 6)
|
||||
assert_equal(result2.y, 12)
|
||||
end)
|
||||
|
||||
test("math.vec2.dot", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local v2 = math.vec2.new(1, 2)
|
||||
assert_equal(math.vec2.dot(v1, v2), 11)
|
||||
assert_close(math.vec2.distance(v1, v2), math.sqrt(8))
|
||||
|
||||
assert_equal(math.vec2.dot(math.vec2.new(1, 0), math.vec2.new(0, 1)), 0)
|
||||
end)
|
||||
|
||||
test("math.vec2.length", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local v2 = math.vec2.new(0, 5)
|
||||
assert_close(math.vec2.length(v1), 5)
|
||||
assert_close(math.vec2.length(v2), 5)
|
||||
end)
|
||||
|
||||
test("math.vec2.distance", function()
|
||||
local v1 = math.vec2.new(0, 0)
|
||||
local v2 = math.vec2.new(3, 4)
|
||||
assert_close(math.vec2.distance(v1, v2), 5)
|
||||
assert_close(math.vec2.distance(math.vec2.new(1, 1), math.vec2.new(4, 5)), 5)
|
||||
end)
|
||||
|
||||
test("math.vec2.normalize", function()
|
||||
local v1 = math.vec2.new(3, 4)
|
||||
local normalized = math.vec2.normalize(v1)
|
||||
assert_close(math.vec2.length(normalized), 1)
|
||||
assert_close(normalized.x, 0.6)
|
||||
assert_close(normalized.y, 0.8)
|
||||
end)
|
||||
|
||||
-- Test 3D vectors
|
||||
test("3D Vectors", function()
|
||||
test("math.vec2.rotate", function()
|
||||
local v1 = math.vec2.new(1, 0)
|
||||
local rotated90 = math.vec2.rotate(v1, math.pi/2)
|
||||
local rotated180 = math.vec2.rotate(v1, math.pi)
|
||||
assert_close(rotated90.x, 0, 1e-10)
|
||||
assert_close(rotated90.y, 1)
|
||||
assert_close(rotated180.x, -1)
|
||||
assert_close(rotated180.y, 0, 1e-10)
|
||||
end)
|
||||
|
||||
-- 3D Vector tests
|
||||
test("math.vec3.new", function()
|
||||
local v1 = math.vec3.new(1, 2, 3)
|
||||
local v2 = math.vec3.new(4, 5, 6)
|
||||
|
||||
assert_close(math.vec3.length(v1), math.sqrt(14))
|
||||
assert_equal(math.vec3.dot(v1, v2), 32)
|
||||
|
||||
local cross = math.vec3.cross(v1, v2)
|
||||
assert_equal(cross.x, -3)
|
||||
assert_equal(cross.y, 6)
|
||||
assert_equal(cross.z, -3)
|
||||
|
||||
local add = math.vec3.add(v1, v2)
|
||||
assert_equal(add.x, 5)
|
||||
assert_equal(add.y, 7)
|
||||
assert_equal(add.z, 9)
|
||||
local v2 = math.vec3.new()
|
||||
assert_equal(v1.x, 1)
|
||||
assert_equal(v1.y, 2)
|
||||
assert_equal(v1.z, 3)
|
||||
assert_equal(v2.x, 0)
|
||||
assert_equal(v2.y, 0)
|
||||
assert_equal(v2.z, 0)
|
||||
end)
|
||||
|
||||
-- Test 2x2 matrices
|
||||
test("2x2 Matrices", function()
|
||||
test("math.vec3.cross", function()
|
||||
local v1 = math.vec3.new(1, 0, 0)
|
||||
local v2 = math.vec3.new(0, 1, 0)
|
||||
local cross = math.vec3.cross(v1, v2)
|
||||
assert_equal(cross.x, 0)
|
||||
assert_equal(cross.y, 0)
|
||||
assert_equal(cross.z, 1)
|
||||
end)
|
||||
|
||||
test("math.vec3.length", function()
|
||||
local v1 = math.vec3.new(1, 2, 3)
|
||||
local v2 = math.vec3.new(0, 0, 5)
|
||||
assert_close(math.vec3.length(v1), math.sqrt(14))
|
||||
assert_close(math.vec3.length(v2), 5)
|
||||
end)
|
||||
|
||||
-- Matrix tests
|
||||
test("math.mat2.det", function()
|
||||
local m1 = math.mat2.new(1, 2, 3, 4)
|
||||
local m2 = math.mat2.new(2, 0, 0, 3)
|
||||
assert_equal(math.mat2.det(m1), -2)
|
||||
assert_equal(math.mat2.det(m2), 6)
|
||||
end)
|
||||
|
||||
test("math.mat2.mul", function()
|
||||
local m1 = math.mat2.new(1, 2, 3, 4)
|
||||
local m2 = math.mat2.new(2, 0, 1, 3)
|
||||
|
||||
assert_equal(math.mat2.det(m1), -2)
|
||||
|
||||
local product = math.mat2.mul(m1, m2)
|
||||
assert_equal(product[1][1], 4)
|
||||
assert_equal(product[1][2], 6)
|
||||
assert_equal(product[2][1], 10)
|
||||
assert_equal(product[2][2], 12)
|
||||
|
||||
local rotation = math.mat2.rotation(math.pi/2)
|
||||
assert_close(rotation[1][1], 0, 1e-10)
|
||||
assert_close(rotation[1][2], -1)
|
||||
assert_close(rotation[2][1], 1)
|
||||
assert_close(rotation[2][2], 0, 1e-10)
|
||||
end)
|
||||
|
||||
-- Test 3x3 matrices
|
||||
test("3x3 Matrices", function()
|
||||
local identity = math.mat3.identity()
|
||||
assert_equal(identity[1][1], 1)
|
||||
assert_equal(identity[2][2], 1)
|
||||
assert_equal(identity[3][3], 1)
|
||||
assert_equal(identity[1][2], 0)
|
||||
|
||||
test("math.mat2.rotation", function()
|
||||
local rot90 = math.mat2.rotation(math.pi/2)
|
||||
local rot180 = math.mat2.rotation(math.pi)
|
||||
assert_close(rot90[1][1], 0, 1e-10)
|
||||
assert_close(rot90[1][2], -1)
|
||||
assert_close(rot180[1][1], -1)
|
||||
assert_close(rot180[2][2], -1)
|
||||
end)
|
||||
|
||||
test("math.mat3.transform_point", function()
|
||||
local translation = math.mat3.translation(5, 10)
|
||||
assert_equal(translation[1][3], 5)
|
||||
assert_equal(translation[2][3], 10)
|
||||
|
||||
local point = {x = 1, y = 2}
|
||||
local transformed = math.mat3.transform_point(translation, point)
|
||||
assert_equal(transformed.x, 6)
|
||||
assert_equal(transformed.y, 12)
|
||||
local point1 = {x = 1, y = 2}
|
||||
local point2 = {x = 0, y = 0}
|
||||
local t1 = math.mat3.transform_point(translation, point1)
|
||||
local t2 = math.mat3.transform_point(translation, point2)
|
||||
assert_equal(t1.x, 6)
|
||||
assert_equal(t1.y, 12)
|
||||
assert_equal(t2.x, 5)
|
||||
assert_equal(t2.y, 10)
|
||||
end)
|
||||
|
||||
-- Test geometry functions
|
||||
test("Geometry", function()
|
||||
assert_close(math.geometry.triangle_area(0, 0, 4, 0, 0, 3), 6)
|
||||
assert_equal(math.geometry.point_in_triangle(1, 1, 0, 0, 4, 0, 0, 3), true)
|
||||
assert_equal(math.geometry.point_in_triangle(5, 5, 0, 0, 4, 0, 0, 3), false)
|
||||
|
||||
local intersects, x, y = math.geometry.line_intersect(0, 0, 2, 2, 0, 2, 2, 0)
|
||||
assert_equal(intersects, true)
|
||||
assert_close(x, 1)
|
||||
assert_close(y, 1)
|
||||
|
||||
local closest_x, closest_y = math.geometry.closest_point_on_segment(1, 3, 0, 0, 4, 0)
|
||||
assert_close(closest_x, 1)
|
||||
assert_close(closest_y, 0)
|
||||
test("math.mat3.det", function()
|
||||
local identity = math.mat3.identity()
|
||||
local scale = math.mat3.scale(2, 3)
|
||||
assert_close(math.mat3.det(identity), 1)
|
||||
assert_close(math.mat3.det(scale), 6)
|
||||
end)
|
||||
|
||||
-- Test interpolation
|
||||
test("Interpolation", function()
|
||||
assert_close(math.interpolation.bezier(0.5, 0, 1, 2, 3), 1.5)
|
||||
assert_close(math.interpolation.quadratic_bezier(0.5, 0, 2, 4), 2)
|
||||
assert_close(math.interpolation.smoothstep(0, 1, 0.5), 0.5)
|
||||
assert_close(math.interpolation.smootherstep(0, 1, 0.5), 0.5)
|
||||
assert_close(math.interpolation.catmull_rom(0.5, 0, 1, 2, 3), 1.5)
|
||||
-- Geometry tests
|
||||
test("math.geometry.triangle_area", function()
|
||||
local area1 = math.geometry.triangle_area(0, 0, 4, 0, 0, 3)
|
||||
local area2 = math.geometry.triangle_area(0, 0, 2, 0, 1, 2)
|
||||
assert_close(area1, 6)
|
||||
assert_close(area2, 2)
|
||||
end)
|
||||
|
||||
test("math.geometry.point_in_triangle", function()
|
||||
local inside1 = math.geometry.point_in_triangle(1, 1, 0, 0, 4, 0, 0, 3)
|
||||
local inside2 = math.geometry.point_in_triangle(5, 5, 0, 0, 4, 0, 0, 3)
|
||||
assert_equal(inside1, true)
|
||||
assert_equal(inside2, false)
|
||||
end)
|
||||
|
||||
test("math.geometry.line_intersect", function()
|
||||
local intersects1, x1, y1 = math.geometry.line_intersect(0, 0, 2, 2, 0, 2, 2, 0)
|
||||
local intersects2, x2, y2 = math.geometry.line_intersect(0, 0, 1, 1, 2, 2, 3, 3)
|
||||
assert_equal(intersects1, true)
|
||||
assert_close(x1, 1)
|
||||
assert_close(y1, 1)
|
||||
assert_equal(intersects2, false)
|
||||
end)
|
||||
|
||||
test("math.geometry.closest_point_on_segment", function()
|
||||
local x1, y1 = math.geometry.closest_point_on_segment(1, 3, 0, 0, 4, 0)
|
||||
local x2, y2 = math.geometry.closest_point_on_segment(5, 1, 0, 0, 4, 0)
|
||||
assert_close(x1, 1)
|
||||
assert_close(y1, 0)
|
||||
assert_close(x2, 4)
|
||||
assert_close(y2, 0)
|
||||
end)
|
||||
|
||||
-- Interpolation tests
|
||||
test("math.interpolation.bezier", function()
|
||||
local result1 = math.interpolation.bezier(0.5, 0, 1, 2, 3)
|
||||
local result2 = math.interpolation.bezier(0, 0, 1, 2, 3)
|
||||
assert_close(result1, 1.5)
|
||||
assert_close(result2, 0)
|
||||
end)
|
||||
|
||||
test("math.interpolation.quadratic_bezier", function()
|
||||
local result1 = math.interpolation.quadratic_bezier(0.5, 0, 2, 4)
|
||||
local result2 = math.interpolation.quadratic_bezier(1, 0, 2, 4)
|
||||
assert_close(result1, 2)
|
||||
assert_close(result2, 4)
|
||||
end)
|
||||
|
||||
test("math.interpolation.smootherstep", function()
|
||||
local result1 = math.interpolation.smootherstep(0, 1, 0.5)
|
||||
local result2 = math.interpolation.smootherstep(0, 10, 5)
|
||||
assert_close(result1, 0.5)
|
||||
assert_close(result2, 0.5)
|
||||
end)
|
||||
|
||||
test("math.interpolation.catmull_rom", function()
|
||||
local result1 = math.interpolation.catmull_rom(0.5, 0, 1, 2, 3)
|
||||
local result2 = math.interpolation.catmull_rom(0, 0, 1, 2, 3)
|
||||
assert_close(result1, 1.5)
|
||||
assert_close(result2, 1)
|
||||
end)
|
||||
|
||||
summary()
|
||||
test_exit()
|
||||
test_exit()
|
||||
|
@ -71,6 +71,16 @@ function assert_table_equal(expected, actual, message, path)
|
||||
return true
|
||||
end
|
||||
|
||||
function assert_close(expected, actual, tolerance, message)
|
||||
tolerance = tolerance or 1e-10
|
||||
local diff = math.abs(expected - actual)
|
||||
if diff <= tolerance then
|
||||
return true
|
||||
end
|
||||
local msg = message or string.format("Expected %g, got %g (diff: %g > %g)", expected, actual, diff, tolerance)
|
||||
assert(false, msg, 3)
|
||||
end
|
||||
|
||||
function test(name, fn)
|
||||
print("Testing " .. name .. "...")
|
||||
total = total + 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user