1
0
Fork 0
mirror of https://github.com/Reuh/wirefame.git synced 2025-10-27 09:39:30 +00:00
This commit is contained in:
Étienne Fildadut 2021-02-18 17:22:21 +01:00
parent b4798c8c16
commit 77cfbaad52
24 changed files with 3033 additions and 1545 deletions

146
math/bb3.lua Normal file
View file

@ -0,0 +1,146 @@
--- Axis aligned 3D bounding box
local bb3
local v3 = require((...):match("^(.*)bb3$").."v3")
-- bb3 methods
-- Unless specified otherwise, all operations are done in place and do not create a bounding box.
local bb3_mt = {
-- Clone the bounding box. Returns a new bounding box.
clone = function(self)
return bb3(self.min:clone(), self.max:clone())
end,
-- Set bounding box
set = function(self, min, max)
self.min, self.max = v3(min), v3(max)
return self
end,
-- Retrieve min and max vectors
unpack = function(self)
return self.min, self.max
end,
-- Test if the bounding box collide with another bounding box, bounding sphere or point
collide = function(self, other)
if other.min then -- bb3
return self.min[1] <= other.max[1] and self.max[1] >= other.min[1] and
self.min[2] <= other.max[2] and self.max[2] >= other.min[2] and
self.min[3] <= other.max[3] and self.max[3] >= other.min[3]
elseif other.radius then -- bs3
return other:collide(self)
else -- v3
return self.min[1] <= other[1] and self.max[1] >= other[1] and
self.min[2] <= other[2] and self.max[2] >= other[2] and
self.min[3] <= other[3] and self.max[3] >= other[3]
end
end,
-- Extend the bounding box by v:
-- * number: extend by a certain distance from the center
-- * v3: extend each axis by the associated vector coordinate
extend = function(self, v)
if type(v) == "number" then
self.min = self.min - { v, v, v }
self.max = self.max + { v, v, v }
else
self.min = self.min - v
self.max = self.max + v
end
return self
end,
-- Modify the bounding box so that it include v (v3 or bb3)
include = function(self, v)
if v.min then -- bounding box
self.min[1] = math.min(self.min[1], v.min[1])
self.min[2] = math.min(self.min[2], v.min[2])
self.min[3] = math.min(self.min[3], v.min[3])
self.max[1] = math.max(self.max[1], v.max[1])
self.max[2] = math.max(self.max[2], v.max[2])
self.max[3] = math.max(self.max[3], v.max[3])
else -- vector
self.min[1] = math.min(self.min[1], v[1])
self.min[2] = math.min(self.min[2], v[2])
self.min[3] = math.min(self.min[3], v[3])
self.max[1] = math.max(self.max[1], v[1])
self.max[2] = math.max(self.max[2], v[2])
self.max[3] = math.max(self.max[3], v[3])
end
return self
end,
-- Common operations with vectors. Returns a new bounding box.
__sub = function(self, other)
return bb3(self.min - other, self.max - other)
end,
__add = function(self, other)
return bb3(self.min + other, self.max + other)
end,
__unm = function(self)
return bb3(-self.min, -self.max)
end,
__tostring = function(self)
return ("bb3(%s,%s)"):format(self.min, self.max)
end
}
bb3_mt.__index = bb3_mt
bb3 = setmetatable({
-- Calculate the mesh's bounding box. Returns a new bounding box.
fromMesh = function(mesh, tr)
local bb
-- Get VertexPosition attribute index in vertex data
local iposition
for i, t in ipairs(mesh:getVertexFormat()) do
if t[1] == "VertexPosition" then
if t[3] ~= 3 then error("mesh vertices don't have a 3 dimensional position") end
iposition = i
break
end
end
if not iposition then error("mesh doesn't have VertexPosition attributes") end
-- Retrieve vertices
local vmap = mesh:getVertexMap()
if vmap then
local min, max = mesh:getDrawRange()
if not min then
min, max = 1, #vmap
end
local v = v3(mesh:getVertexAttribute(vmap[min], iposition))
if tr then v:transform(tr) end
bb = bb3(v:clone(),v:clone())
for i=min+1, max do
v = v3(mesh:getVertexAttribute(vmap[i], iposition))
if tr then v:transform(tr) end
bb:include(v)
end
else
local min, max = mesh:getDrawRange()
if not min then
min, max = 1, mesh:getVertexCount()
end
local v = v3(mesh:getVertexAttribute(min, iposition))
if tr then v:transform(tr) end
bb = bb3(v:clone(),v:clone())
for i=min+1, max do
v = v3(mesh:getVertexAttribute(i, iposition))
if tr then v:transform(tr) end
bb:include(v)
end
end
return bb
end
}, {
-- min and max will not be copied.
__call = function(self, min, max)
return setmetatable({ min = v3(min), max = v3(max) }, bb3_mt)
end
})
return bb3

145
math/bs3.lua Normal file
View file

@ -0,0 +1,145 @@
--- 3D bounding sphere
local bs3
local v3 = require((...):match("^(.*)bs3$").."v3")
-- bs3 methods
-- Unless specified otherwise, all operations are done in place and do not create a bounding sphere.
local bs3_mt = {
-- Clone the bounding sphere. Returns a new bounding sphere.
clone = function(self)
return bs3(self.center:clone(), self.radius)
end,
-- Set bounding sphere
set = function(self, center, radius)
self.center:set(center)
self.radius = radius
return self
end,
-- Retrieve center vectors and radius
unpack = function(self)
return self.center, self.radius
end,
-- Test if the bounding sphere collide with another bounding sphere, bounding box or point
collide = function(self, other)
if other.radius then -- bs3
return (self.center[1] - other.center[1]) * (self.center[1] - other.center[1]) +
(self.center[2] - other.center[2]) * (self.center[2] - other.center[2]) +
(self.center[3] - other.center[3]) * (self.center[3] - other.center[3])
<= (self.radius + other.radius) * (self.radius + other.radius)
elseif other.min then -- bb3
local x = math.max(other.min[1], math.min(self.center[1], other.max[1]))
local y = math.max(other.min[2], math.min(self.center[2], other.max[2]))
local z = math.max(other.min[3], math.min(self.center[3], other.max[3]))
return (self.center[1] - x) * (self.center[1] - x) +
(self.center[2] - y) * (self.center[2] - y) +
(self.center[3] - z) * (self.center[3] - z)
<= self.radius * self.radius
else -- v3
return (self.center[1] - other[1]) * (self.center[1] - other[1]) +
(self.center[2] - other[2]) * (self.center[2] - other[2]) +
(self.center[3] - other[3]) * (self.center[3] - other[3])
<= self.radius * self.radius
end
end,
-- Extend the bounding sphere by v (number)
extend = function(self, v)
self.radius = self.radius + v
return self
end,
-- Modify the bounding sphere so that it include v (v3 or bs3)
include = function(self, v)
if v.min then -- bounding sphere
self.radius = (self.center:distance(v.center) + self.radius + v.radius) / 2
self.center = v.center + (self.center - v.center):normalize() * (self.radius - v.radius)
else -- vector
self.radius = (self.center:distance(v) + self.radius) / 2
self.center = v + (self.center - v):normalize() * self.radius
end
return self
end,
-- Common operations with vectors. Returns a new bounding box.
__sub = function(self, other)
return bs3(self.center - other, self.radius)
end,
__add = function(self, other)
return bs3(self.center + other, self.radius)
end,
__unm = function(self)
return bs3(-self.center, self.radius)
end,
__div = function(self, other)
return bs3(self.center, self.radius / other)
end,
__mul = function(self, other)
return bs3(self.center, self.radius * other)
end,
__tostring = function(self)
return ("bs3(%s,%s)"):format(self.center, self.radius)
end
}
bs3_mt.__index = bs3_mt
bs3 = setmetatable({
-- Calculate the mesh's bounding sphere. Returns a new bounding sphere.
fromMesh = function(mesh, tr)
local bs
-- Get VertexPosition attribute index in vertex data
local iposition
for i, t in ipairs(mesh:getVertexFormat()) do
if t[1] == "VertexPosition" then
if t[3] ~= 3 then error("mesh vertices don't have a 3 dimensional position") end
iposition = i
break
end
end
if not iposition then error("mesh doesn't have VertexPosition attributes") end
-- Retrieve vertices
local vmap = mesh:getVertexMap()
if vmap then
local min, max = mesh:getDrawRange()
if not min then
min, max = 1, #vmap
end
local v = v3(mesh:getVertexAttribute(vmap[min], iposition))
if tr then v:transform(tr) end
bs = bs3(v:clone(),0)
for i=min+1, max do
v = v3(mesh:getVertexAttribute(vmap[i], iposition))
if tr then v:transform(tr) end
bs:include(v)
end
else
local min, max = mesh:getDrawRange()
if not min then
min, max = 1, mesh:getVertexCount()
end
local v = v3(mesh:getVertexAttribute(min, iposition))
if tr then v:transform(tr) end
bs = bs3(v:clone(),0)
for i=min+1, max do
v = v3(mesh:getVertexAttribute(i, iposition))
if tr then v:transform(tr) end
bs:include(v)
end
end
return bs
end
}, {
-- center will not be copied.
__call = function(self, center, radius)
return setmetatable({ center = v3(center), radius = radius }, bs3_mt)
end
})
return bs3

246
math/m4.lua Normal file
View file

@ -0,0 +1,246 @@
--- 4x4 matrix.
-- Reminder: they are represented in row-major order, **unlike** GLM which is column-major.
local m4
local cos, sin, tan = math.cos, math.sin, math.tan
local v3 = require((...):match("^(.*)m4$").."v3")
local tmpv3 = v3(0,0,0) -- temporary vector used in our computations
-- m4 methods
-- Unless specified otherwise, all operations are done in place and do not create a new matrix.
local m4_mt = {
-- Clone the matrix. Returns a new matrix.
clone = function(self)
return m4{
self[1], self[2], self[3], self[4],
self[5], self[6], self[7], self[8],
self[9], self[10], self[11], self[12],
self[13], self[14], self[15], self[16]
}
end,
-- Set matrix
set = function(self, other)
self[1], self[2], self[3], self[4] = other[1], other[2], other[3], other[4]
self[5], self[6], self[7], self[8] = other[5], other[6], other[7], other[8]
self[9], self[10], self[11], self[12] = other[9], other[10], other[11], other[12]
self[13], self[14], self[15], self[16] = other[13], other[14], other[15], other[16]
return self
end,
-- Retrieve values
unpack = function(self)
return self[1], self[2], self[3], self[4],
self[5], self[6], self[7], self[8],
self[9], self[10], self[11], self[12],
self[13], self[14], self[15], self[16]
end,
-- Apply a transformation matrix to our matrix
transform = function(self, tr)
local s1, s2, s3, s4 = self[1], self[2], self[3], self[4]
local s5, s6, s7, s8 = self[5], self[6], self[7], self[8]
local s9, s10, s11, s12 = self[9], self[10], self[11], self[12]
local s13, s14, s15, s16 = self[13], self[14], self[15], self[16]
self[1] = tr[1] * s1 + tr[2] * s5 + tr[3] * s9 + tr[4] * s13
self[2] = tr[1] * s2 + tr[2] * s6 + tr[3] * s10 + tr[4] * s14
self[3] = tr[1] * s3 + tr[2] * s7 + tr[3] * s11 + tr[4] * s15
self[4] = tr[1] * s4 + tr[2] * s8 + tr[3] * s12 + tr[4] * s16
self[5] = tr[5] * s1 + tr[6] * s5 + tr[7] * s9 + tr[8] * s13
self[6] = tr[5] * s2 + tr[6] * s6 + tr[7] * s10 + tr[8] * s14
self[7] = tr[5] * s3 + tr[6] * s7 + tr[7] * s11 + tr[8] * s15
self[8] = tr[5] * s4 + tr[6] * s8 + tr[7] * s12 + tr[8] * s16
self[9] = tr[9] * s1 + tr[10] * s5 + tr[11] * s9 + tr[12] * s13
self[10] = tr[9] * s2 + tr[10] * s6 + tr[11] * s10 + tr[12] * s14
self[11] = tr[9] * s3 + tr[10] * s7 + tr[11] * s11 + tr[12] * s15
self[12] = tr[9] * s4 + tr[10] * s8 + tr[11] * s12 + tr[12] * s16
self[13] = tr[13] * s1 + tr[14] * s5 + tr[15] * s9 + tr[16] * s13
self[14] = tr[13] * s2 + tr[14] * s6 + tr[15] * s10 + tr[16] * s14
self[15] = tr[13] * s3 + tr[14] * s7 + tr[15] * s11 + tr[16] * s15
self[16] = tr[13] * s4 + tr[14] * s8 + tr[15] * s12 + tr[16] * s16
return self
end,
translate = function(self, v)
return self:transform(m4.translate(v))
end,
scale = function(self, v)
return self:transform(m4.scale(v))
end,
rotate = function(self, angle, v)
return self:transform(m4.rotate(angle, v))
end,
shear = function(self, vxy, vyz)
return self:transform(m4.shear(vxy, vyz))
end,
-- Common operations. Returns a new matrix.
__mul = function(self, other)
return m4{
self[1] * other[1] + self[2] * other[5] + self[3] * other[9] + self[4] * other[13],
self[1] * other[2] + self[2] * other[6] + self[3] * other[10] + self[4] * other[14],
self[1] * other[3] + self[2] * other[7] + self[3] * other[11] + self[4] * other[15],
self[1] * other[4] + self[2] * other[8] + self[3] * other[12] + self[4] * other[16],
self[5] * other[1] + self[6] * other[5] + self[7] * other[9] + self[8] * other[13],
self[5] * other[2] + self[6] * other[6] + self[7] * other[10] + self[8] * other[14],
self[5] * other[3] + self[6] * other[7] + self[7] * other[11] + self[8] * other[15],
self[5] * other[4] + self[6] * other[8] + self[7] * other[12] + self[8] * other[16],
self[9] * other[1] + self[10] * other[5] + self[11] * other[9] + self[12] * other[13],
self[9] * other[2] + self[10] * other[6] + self[11] * other[10] + self[12] * other[14],
self[9] * other[3] + self[10] * other[7] + self[11] * other[11] + self[12] * other[15],
self[9] * other[4] + self[10] * other[8] + self[11] * other[12] + self[12] * other[16],
self[13] * other[1] + self[14] * other[5] + self[15] * other[9] + self[16] * other[13],
self[13] * other[2] + self[14] * other[6] + self[15] * other[10] + self[16] * other[14],
self[13] * other[3] + self[14] * other[7] + self[15] * other[11] + self[16] * other[15],
self[13] * other[4] + self[14] * other[8] + self[15] * other[12] + self[16] * other[16]
}
end,
__add = function(self, other)
return m4{
self[1] + other[1], self[2] + other[2], self[3] + other[3], self[4] + other[4],
self[5] + other[5], self[6] + other[6], self[7] + other[7], self[8] + other[8],
self[9] + other[9], self[10] + other[10], self[11] + other[11], self[12] + other[12],
self[13] + other[13], self[14] + other[14], self[15] + other[15], self[16] + other[16],
}
end,
__sub = function(self, other)
return m4{
self[1] - other[1], self[2] - other[2], self[3] - other[3], self[4] - other[4],
self[5] - other[5], self[6] - other[6], self[7] - other[7], self[8] - other[8],
self[9] - other[9], self[10] - other[10], self[11] - other[11], self[12] - other[12],
self[13] - other[13], self[14] - other[14], self[15] - other[15], self[16] - other[16],
}
end,
__unm = function(self)
return m4{
-self[1], -self[2], -self[3], -self[4],
-self[5], -self[6], -self[7], -self[8],
-self[9], -self[10], -self[11], -self[12],
-self[13], -self[14], -self[15], -self[16]
}
end,
__tostring = function(self)
local mt = getmetatable(self)
setmetatable(self, nil)
local str = "m4: "..(tostring(self):gsub("^table: ", ""))
setmetatable(self, mt)
return str
end
}
m4_mt.__index = m4_mt
m4 = setmetatable({
-- Common transformation to create new matrices from
identity = function()
return m4{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
}
end,
translate = function(v)
return m4{
1, 0, 0, v[1],
0, 1, 0, v[2],
0, 0, 1, v[3],
0, 0, 0, 1
}
end,
scale = function(v)
return m4{
v[1], 0, 0, 0,
0, v[2], 0, 0,
0, 0, v[3], 0,
0, 0, 0, 1
}
end,
rotate = function(angle, v)
local c = cos(angle)
local s = sin(angle)
local x, y, z = tmpv3:set(v):normalize():unpack()
local tx, ty, tz = (1 - c) * x, (1 - c) * y, (1 - c) * z
return m4{
c + tx * x, ty * x - s * z, tz * x + s * y, 0,
tx * y + s * z, c + ty * y, tz * y - s * x, 0,
tx * z - s * y, ty * z + s * x, c + tz * z, 0,
0, 0, 0, 1
}
end,
-- vxy: shearing vector in the (xy) plane
-- vyz: shearing vector in the (yz) plane
shear = function(vxy, vyz)
return m4{
1, vxy[1], vyz[1], 0,
vxy[2], 1, vyz[2], 0,
vxy[3], vyz[3], 1, 0,
0, 0, 0, 1
}
end,
-- Projection matrix constructor (right-handed).
perspective = function(fovy, aspect, zNear, zFar) -- If zFar is not specified, will return an infinite perspective matrix.
local f = 1 / tan(fovy / 2)
if zFar then
return m4{
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (zFar + zNear) / (zNear - zFar), (2 * zFar * zNear) / (zNear - zFar),
0, 0, -1, 0
}
else
return m4{
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, -1, -2 * zNear,
0, 0, -1, 0
}
end
end,
orthographic = function(xmag, ymag, znear, zfar)
return m4{
1 / xmag, 0, 0, 0,
0, 1 / ymag, 0, 0,
0, 0, 2 / (znear - zfar), (zfar + znear) / (znear - zfar),
0, 0, 0, 1
}
end,
lookAt = function(eye, center, up)
center, up = v3(center), v3(up)
local f = (center - eye):normalize()
local s = f:cross(up):normalize()
local u = s:cross(f)
return m4{
s[1], s[2], s[3], -s:dot(eye),
u[1], u[2], u[3], -u:dot(eye),
-f[1], -f[2], -f[3], f:dot(eye),
0, 0, 0, 1
}
end,
-- Create a new matrix from column major list
fromColumnMajor = function(m)
return m4{
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15],
m[4], m[8], m[12], m[16]
}
end
}, {
-- Will not copy the table.
__call = function(self, t)
return setmetatable(t, m4_mt)
end
})
return m4

90
math/qt.lua Normal file
View file

@ -0,0 +1,90 @@
--- Quaternion
local qt
local sin, cos = math.sin, math.cos
local m4 = require((...):match("^(.*)qt$").."m4")
-- qt methods
-- Unless specified otherwise, all operations are done in place and do not create a new quaternion.
local qt_mt = {
-- Clone the quaternion. Returns a new quaternion.
clone = function(self)
return qt(self[1], self[2], self[3], self[4])
end,
-- Set quaternion
set = function(self, other)
self[1], self[2], self[3], self[4] = other[1], other[2], other[3], other[4]
return self
end,
-- Retrieve coordinates
unpack = function(self)
return self[1], self[2], self[3], self[4]
end,
-- Convert to m4 rotation matrix. Returns a new 4x4 matrix.
toM4 = function(self)
local x, y, z, w = self[1], self[2], self[3], self[4]
local xx, yy, zz = x*x, y*y, z*z
local xw, xy, xz = x*w, x*y, x*z
local yw, yz = y*w, y*z
local zw = z*w
return m4{
1-2*(yy+zz), 2*(xy-zw), 2*(xz+yw), 0,
2*(xy+zw), 1-2*(xx+zz), 2*(yz-xw), 0,
2*(xz-yw), 2*(yz+xw), 1-2*(xx+yy), 0,
0, 0, 0, 1
}
end,
-- Common operations. Returns a new quaternion.
__sub = function(self, other)
return qt(self[1] - other[1], self[2] - other[2], self[3] - other[3], self[4] - other[4])
end,
__add = function(self, other)
return qt(self[1] + other[1], self[2] + other[2], self[3] + other[3], self[4] - other[4])
end,
__unm = function(self)
return qt(-self[1], -self[2], -self[3], -self[4])
end,
__mul = function(self, other)
local sx, sy, sz, sw = self[1], self[2], self[3], self[4]
local ox, oy, oz, ow = other[1], other[2], other[3], other[4]
return qt(sw * ow - sx * ox - sy * oy - sz * oz, sx * ow + sw * ox + sy * oz - sz * oy, sy * ow + sw * oy + sz * ox - sx * oz, sz * ow + sw * oz + sx * oy - sy * ox)
end,
__eq = function(self, other)
return self[1] == other[1] and self[2] == other[2] and self[3] == other[3] and self[4] == other[4]
end,
__tostring = function(self)
return ("qt(%s,%s,%s,%s)"):format(self[1], self[2], self[3], self[4])
end
}
qt_mt.__index = qt_mt
qt = setmetatable({
--- Create a new identity quaternion
identity = function()
return qt(0, 0, 0, 1)
end,
--- Create a new quaternion from a rotation
fromAngleAxis = function(angle, axis)
local halfAngle = angle / 2
local s = sin(halfAngle)
return qt(axis[1] * s, axis[2] * s, axis[3] * s, cos(halfAngle))
end,
}, {
-- If x is a table, will reuse it without copying.
__call = function(self, x, y, z, w)
if type(x) == "number" then
return setmetatable({ x, y, z, w }, qt_mt)
else
return setmetatable(x, qt_mt)
end
end
})
return qt

114
math/v3.lua Normal file
View file

@ -0,0 +1,114 @@
--- 3D vector
local v3
local sqrt = math.sqrt
-- v3 methods
-- Unless specified otherwise, all operations are done in place and do not create a new vector.
local v3_mt = {
-- Clone the vector. Returns a new vector.
clone = function(self)
return v3(self[1], self[2], self[3])
end,
-- Set vector
set = function(self, other)
self[1], self[2], self[3] = other[1], other[2], other[3]
return self
end,
-- Retrieve coordinates
unpack = function(self)
return self[1], self[2], self[3]
end,
-- Normalize
normalize = function(self)
local x, y, z = self[1], self[2], self[3]
local l = sqrt(x*x + y*y + z*z)
self[1], self[2], self[3] = x/l, y/l, z/l
return self
end,
-- Vector product. Returns a new vector.
cross = function(self, other)
local x1, y1, z1 = self[1], self[2], self[3]
local x2, y2, z2 = other[1], other[2], other[3]
return v3(
y1*z2 - z1*y2,
z1*x2 - x1*z2,
x1*y2 - y1*x2
)
end,
dot = function(self, other)
return self[1] * other[1] + self[2] * other[2] + self[3] * other[3]
end,
-- Transform by a mat4
transform = function(self, matrix)
local x, y, z = self[1], self[2], self[3]
-- Transform matrix * Homogeneous coordinates
local mx = matrix[1] * x + matrix[2] * y + matrix[3] * z + matrix[4]
local my = matrix[5] * x + matrix[6] * y + matrix[7] * z + matrix[8]
local mz = matrix[9] * x + matrix[10] * y + matrix[11] * z + matrix[12]
local mw = matrix[13] * x + matrix[14] * y + matrix[15] * z + matrix[16]
-- Go back to euclidian coordinates
self[1], self[2], self[3] = mx/mw, my/mw, mz/mw
return self
end,
-- Length
len2 = function(self)
local x, y, z = self[1], self[2], self[3]
return x*x + y*y + z*z
end,
len = function(self)
local x, y, z = self[1], self[2], self[3]
return sqrt(x*x + y*y + z*z)
end,
-- Distance
distance = function(self, other)
return (self - other):len()
end,
distance2 = function(self, other)
return (self - other):len2()
end,
-- Common operations. Returns a new vector.
__sub = function(self, other)
return v3(self[1] - other[1], self[2] - other[2], self[3] - other[3])
end,
__add = function(self, other)
return v3(self[1] + other[1], self[2] + other[2], self[3] + other[3])
end,
__unm = function(self)
return v3(-self[1], -self[2], -self[3])
end,
__div = function(self, other)
return v3(self[1] / other, self[2] / other, self[3] / other)
end,
__mul = function(self, other)
return v3(self[1] * other, self[2] * other, self[3] * other)
end,
__eq = function(self, other)
return self[1] == other[1] and self[2] == other[2] and self[3] == other[3]
end,
__tostring = function(self)
return ("v3(%s,%s,%s)"):format(self[1], self[2], self[3])
end
}
v3_mt.__index = v3_mt
-- If x is a table, will reuse it without copying.
v3 = function(x, y, z)
if type(x) == "number" then
return setmetatable({ x, y, z }, v3_mt)
else
return setmetatable(x, v3_mt)
end
end
return v3