--- lua-tikz3dtools-vector.lua --- Vector class for the lua-tikz3dtools package. local Vector = {} Vector.__index = Vector --- Create a new 1xn Vector object (validated). --- @param vec table The 1xn vector data --- @return Vector A 1xn vector function Vector:new(vec) local A = type(vec) == "table" local n = #vec A = A and n > 0 if A == true then for c = 1, n do A = A and type(vec[c]) == "number" end end assert(A, "A Vector object must be a 1xn table of numbers, where n>0.") return setmetatable(vec, self) end --- Unchecked constructor — skips validation. Use for internal calls only. --- @param vec table --- @return Vector function Vector:_new(vec) return setmetatable(vec, self) end --- Pretty print for Vector object --- @return string function Vector:__tostring() return ("Vector{%.12f" .. string.rep(",%.12f", #self - 1) .. "}"):format(table.unpack(self)) end --- Deep copy of Vector object --- @return Vector A deep copy of self function Vector:deep_copy() local n = #self local copy = {} for i = 1, n do copy[i] = self[i] end return Vector:_new(copy) end --- Convert Vector to table --- @return table A table representation of self function Vector:to_table() local n = #self local t = {} for i = 1, n do t[i] = self[i] end return t end --- Homogeneous addition of Vector objects --- @param other Vector A vector of same size --- @return Vector The sum of self and other function Vector:hadd(other) local lo = #other local V = {} for i = 1, lo - 1 do V[i] = self[i] + other[i] end V[lo] = self[lo] return Vector:_new(V) end --- Addition of Vector objects --- @param other Vector A vector of same size --- @return Vector The sum of self and other function Vector:add(other) local lo = #other local V = {} for i = 1, lo do V[i] = self[i] + other[i] end return Vector:_new(V) end --- Homogeneous subtraction of Vector objects --- @param other Vector A vector of same size --- @return Vector The difference of self and other function Vector:hsub(other) local lo = #other local V = {} for i = 1, lo - 1 do V[i] = self[i] - other[i] end V[lo] = self[lo] return Vector:_new(V) end --- Subtraction of Vector objects --- @param other Vector A vector of same size --- @return Vector The difference of self and other function Vector:sub(other) local lo = #other local V = {} for i = 1, lo do V[i] = self[i] - other[i] end return Vector:_new(V) end --- Homogeneous scaling of Vector objects --- @param scalar number The scalar --- @return Vector The scale of self by scalar function Vector:hscale(scalar) local ls = #self local V = {} for i = 1, ls - 1 do V[i] = self[i] * scalar end V[ls] = self[ls] return Vector:_new(V) end --- Scaling of Vector objects --- @param scalar number The scalar --- @return Vector The scale of self by scalar function Vector:scale(scalar) local ls = #self local V = {} for i = 1, ls do V[i] = self[i] * scalar end return Vector:_new(V) end --- Homogeneous inner product of Vector objects --- @param other Vector The RHS --- @return number The homogeneous inner product of self with other function Vector:hinner(other) local ls = #self local result = 0 for i = 1, ls - 1 do result = result + self[i] * other[i] end return result end --- Homogeneous hypercross product of Vector objects --- @param ... Vector Additional vectors --- @return Vector The homogeneous hypercross product of self with the additional vectors function Vector:hhypercross(...) -- Forward-declared; Matrix is set via Vector._set_Matrix() local Matrix = Vector._Matrix local args = { ... } local n = #self - 1 local M = {} for i = 1, n do local e = {} for j = 1, n do e[j] = (i == j) and 1 or 0 end e[n + 1] = 0 M[i] = {} M[i][1] = Vector:_new(e) M[i][2] = self[i] for j = 1, n - 2 do M[i][j + 2] = args[j][i] end end local result = Matrix:_new(M):det("column", 1) result[n + 1] = self[n + 1] return Vector:_new(result) end --- Homogeneous normalization of Vector objects --- @return Vector The homogeneous normalization of self function Vector:hnormalize() local ls = #self local len = 0 for i = 1, ls - 1 do len = len + self[i] * self[i] end len = math.sqrt(len) local V = {} for i = 1, ls - 1 do V[i] = self[i] / len end V[ls] = self[ls] return Vector:_new(V) end --- Homogeneous distance between Vector objects --- @param other Vector A vector of same size --- @return number The homogeneous distance between self and other function Vector:hdistance(other) local ls = #self local result = 0 for i = 1, ls - 1 do result = result + (self[i] - other[i])^2 end return math.sqrt(result) end --- Homogeneous projection of Vector objects --- @param other Vector A vector of same size --- @return Vector The homogeneous projection of self onto other function Vector:hproject_onto(other) local denom = other:hinner(other) local scalar = self:hinner(other) / denom return other:hscale(scalar) end --- Homogeneous orthogonal projection of Vector objects onto a plane --- @param plane_basis table A 3-row basis (origin, u-dir, v-dir) in homogeneous coordinates --- @return Vector The homogeneous orthogonal projection of self onto the plane function Vector:horthogonal_projection_onto_plane(plane_basis) local Matrix = Vector._Matrix local TO = Vector:_new(plane_basis[1]) local TU = Vector:_new(plane_basis[2]) local TV = Vector:_new(plane_basis[3]) local TW = TU:hhypercross(TV) local TOS = self:hsub(TO) if TW:hinner(TOS) < 0 then TW = TW:hscale(-1) end local proj = TOS:hproject_onto(TW) return self:hsub(proj) end --- Homogeneous norm of Vector objects --- @return number The homogeneous norm of self function Vector:hnorm() local sum = 0 for i = 1, #self - 1 do sum = sum + self[i] * self[i] end return math.sqrt(sum) end --- Homogeneous reciprocation of Vector objects --- @return Vector The homogeneous reciprocation of self function Vector:reciprocate_by_homogeneous() local V = {} local w = self[#self] for i = 1, #self - 1 do V[i] = self[i] / w end V[#self] = 1 return Vector:_new(V) end --- Find an arbitrary vector orthogonal to self --- @return Vector An arbitrary vector orthogonal to self function Vector:orthogonal_vector() local v if ( math.abs(self[2]) > 1e-3 and math.abs(self[1]) < 1e-3 and math.abs(self[3]) < 1e-3 ) then v = self:hhypercross(Vector:_new{0,1,0,1}) else v = self:hhypercross(Vector:_new{1,0,0,1}) end return v end --- Multiply Vector by Matrix --- @param matrix table The matrix --- @return Vector The product of self and matrix function Vector:multiply(matrix) local Matrix = Vector._Matrix local M = Matrix:_new{{table.unpack(self)}}:multiply(matrix) return Vector:_new(M[1]) end --- Create a homogeneous sphere point from spherical coordinates --- @param theta number The azimuthal angle --- @param phi number The polar angle --- @param radius number The radius --- @return Vector function Vector.hsphere3(theta, phi, radius) local x = radius * math.sin(phi) * math.cos(theta) local y = radius * math.sin(phi) * math.sin(theta) local z = radius * math.cos(phi) return Vector:_new{x, y, z, 1} end --- Set the Matrix class reference (breaks circular dependency). --- @param M table The Matrix class function Vector._set_Matrix(M) Vector._Matrix = M end return Vector