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