1
0
forked from redo/BlockLua
Files
BlockLua/src/util/vector.lua
2025-10-06 13:11:57 -04:00

242 lines
6.7 KiB
Lua

-- Vector math class with operators
local vector_meta
local vector_new
local function vector_check(v, n, name, argn)
if not v.__is_vector then
error('vector ' .. name .. ': argument #' .. (argn or 1)
.. ': expected vector, got ' .. type(v), n + 1)
end
end
local function vector_checksamelen(v1, v2, name)
vector_check(v1, 3, name, 1)
vector_check(v2, 3, name, 2)
if #v1 ~= #v2 then
error('vector ' .. name .. ': vector lengths do not match (lengths are '
.. #v1 .. ' and ' .. #v2 .. ')', 3)
end
return #v1
end
local function vector_checklen(v1, v2, name, len)
vector_check(v1, 3, name, 1)
vector_check(v2, 3, name, 2)
if #v1 ~= len or #v2 ~= len then
error('vector ' .. name .. ': vector lengths are not ' .. len .. ' (lengths are '
.. #v1 .. ' and ' .. #v2 .. ')', 3)
end
end
local function vector_opnnn(name, op)
return function(v1, v2)
local len = vector_checksamelen(v1, v2, name)
local v3 = {}
for i = 1, len do
v3[i] = op(v1[i], v2[i])
end
return vector_new(v3)
end
end
local function vector_opnxn(name, op)
return function(v1, v2)
local v1v = type(v1) == 'table' and v1.__is_vector
local v2v = type(v2) == 'table' and v2.__is_vector
if v1v and v2v then
local len = vector_checksamelen(v1, v2, name)
local v3 = {}
for i = 1, len do
v3[i] = op(v1[i], v2[i])
end
return vector_new(v3)
else
if v2v then v1, v2 = v2, v1 end
local len = #v1
local v3 = {}
for i = 1, len do
v3[i] = op(v1[i], v2)
end
return vector_new(v3)
end
end
end
local function vector_opn0n(name, op)
return function(v1)
--vector_check(v1, 1, name)
local len = #v1
local v2 = {}
for i = 1, len do
v2[i] = op(v1[i])
end
return vector_new(v2)
end
end
local vector_indices = { x = 1, y = 2, z = 3, w = 4, r = 1, g = 2, b = 3, a = 4 }
local vector_meta = {
__is_vector = true,
__index = function(t, k)
if tonumber(k) then
return rawget(t, k)
elseif vector_indices[k] then
return rawget(t, vector_indices[k])
else
return getmetatable(t)[k]
end
end,
__newindex = function(t, k, v)
if tonumber(k) then
rawset(t, k, v)
elseif vector_indices[k] then
rawset(t, vector_indices[k], v)
else
return
end
end,
__add = vector_opnnn('add', function(x1, x2) return x1 + x2 end),
__sub = vector_opnnn('sub', function(x1, x2) return x1 - x2 end),
__mul = vector_opnxn('mul', function(x1, x2) return x1 * x2 end),
__div = vector_opnxn('div', function(x1, x2) return x1 / x2 end),
__pow = vector_opnxn('pow', function(x1, x2) return x1 ^ x2 end),
__unm = vector_opn0n('inv', function(x1) return -x1 end),
__concat = nil,
--__len = function(v1) return #v1 end,
__len = nil,
__eq = function(v1, v2)
local len = vector_checksamelen(v1, v2, 'equals')
for i = 1, len do
if v1[i] ~= v2[i] then return false end
end
return true
end,
__lt = nil,
__le = nil,
__call = nil,
abs = vector_opn0n('abs', math.abs),
length = function(v1)
--vector_check(v1, 2, 'length')
local len = #v1
local l = 0
for i = 1, len do
l = l + v1[i] ^ 2
end
return math.sqrt(l)
end,
normalize = function(v1)
--vector_check(v1, 2, 'normal')
local length = v1:length()
local len = #v1
local v3 = {}
for i = 1, len do
if length == 0 then
v3[i] = 0
else
v3[i] = v1[i] / length
end
end
return vector_new(v3)
end,
__tostring = function(v1)
--vector_check(v1, 2, 'tostring')
local st = {}
local len = #v1
for i = 1, len do
table.insert(st, tostring(v1[i]))
end
return 'vector{ ' .. table.concat(st, ', ') .. ' }'
end,
unpack = function(v1) return unpack(v1) end,
floor = vector_opn0n('floor', function(x1) return math.floor(x1) end),
ceil = vector_opn0n('ceil', function(x1) return math.ceil(x1) end),
round = vector_opn0n('round', function(x1) return math.floor(x1 + 0.5) end),
dot = function(v1, v2)
local len = vector_checksamelen(v1, v2, 'dot')
local x = 0
for i = 1, len do
x = x + v1[i] * v2[i]
end
return x
end,
cross = function(v1, v2)
vector_checklen(v1, v2, 'cross', 3)
return vector_new {
v1[2] * v2[3] - v1[3] * v2[2],
v1[3] * v2[1] - v1[1] * v2[3],
v1[1] * v2[2] - v1[2] * v2[1],
}
end,
rotateByAngleId = function(v1, r)
--vector_check(v1, 2, 'rotate')
if type(r) ~= 'number' or r % 1 ~= 0 then
error('vector rotateByAngleId: invalid rotation ' .. tostring(r), 2)
end
r = r % 4
local v2
if r == 0 then
v2 = vector_new { v1[1], v1[2], v1[3] }
elseif r == 1 then
v2 = vector_new { v1[2], -v1[1], v1[3] }
elseif r == 2 then
v2 = vector_new { -v1[1], -v1[2], v1[3] }
elseif r == 3 then
v2 = vector_new { -v1[2], v1[1], v1[3] }
else
error('vector rotateByAngleId: invalid rotation ' .. r, 2)
end
return v2
end,
rotateZ = function(v, r)
--vector_check(v, 2, 'rotate2d')
if type(r) ~= 'number' then
error('vector rotateZ: invalid rotation ' .. tostring(r), 2)
end
local len = math.sqrt(v[1] ^ 2 + v[2] ^ 2)
local ang = math.atan2(v[2], v[1]) + r
local v2 = vector_new { math.cos(ang) * len, math.sin(ang) * len }
return v2
end,
tsString = function(v)
--vector_check(v, 2, 'tsString')
return table.concat(v, ' ')
end,
distance = function(v1, v2)
local len = vector_checksamelen(v1, v2, 'distance')
local sum = 0
for i = 1, len do
sum = sum + (v1[i] - v2[i]) ^ 2
end
return math.sqrt(sum)
end,
copy = function(v)
--vector_check(v, 2, 'copy')
return vector_new(v)
end,
}
vector_new = function(vi)
if vi then
if type(vi) == 'string' then
local vi2 = {}
for val in vi:gmatch('[0-9%.%-e]+') do
table.insert(vi2, tonumber(val))
end
vi = vi2
elseif type(vi) ~= 'table' then
error('vector: argument #1: expected input table, got ' .. type(vi), 2)
end
local v = {}
if #vi > 0 then
for i = 1, #vi do v[i] = vi[i] end
else
for n, i in pairs(vector_indices) do v[i] = vi[n] end
if #v == 0 then
error('vector: argument #1: table contains no values', 2)
end
end
setmetatable(v, vector_meta)
return v
else
error('vector: argument #1: expected input table, got nil', 2)
end
end
vector = vector_new
print(' Executed vector.lua')
return vector_new