-- 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 return vector_new