diff --git a/all.lua b/all.lua new file mode 100644 index 0000000000000000000000000000000000000000..3834ea8af129a952137c66671170349b14bb4b72 --- /dev/null +++ b/all.lua @@ -0,0 +1,5 @@ +require("kara3d.vector") +require("kara3d.matrix") +require("kara3d.quaternion") +require("kara3d.transform") +require("kara3d.shape") diff --git a/matrix.lua b/matrix.lua new file mode 100644 index 0000000000000000000000000000000000000000..e9945c07fba9b0157e457581c2e3a2a2b10c4da1 --- /dev/null +++ b/matrix.lua @@ -0,0 +1,142 @@ +require("kara3d.vector") + +matrix = {class = "matrix"} +matrix.__index = matrix + +function matrix.new(r, c, vals) + local m = {rows = r or 1, cols = c or 1, values = {}} + vals = vals or {} + for i = 1, r*c do + m.values[i] = i <= #vals and vals[i] or 0 + end + setmetatable(m, matrix) + return m +end + +function matrix:tostring() + local str = self.rows .. " x " .. self.cols .. "\n" + for i = 1, self.rows do + for j = 1, self.cols do + str = str .. self:get(i, j) .. (j < self.cols and "\t" or "\n") + end + end + return str +end + +function matrix.identity(r, c) + local m = matrix.new(r, c) + for i = 1, math.min(r, c) do m:set(i, i, 1) end + return m +end + +function matrix:clone() + return matrix.new(self.rows, self.cols, self.values) +end + +function matrix:get(i, j) + if (i <= 0 or i > self.rows) then return 0 end + if (j <= 0 or j > self.cols) then return 0 end + local n = (i-1) * self.cols + j + return n > #self.values and 0 or self.values[n] +end + +function matrix:column(i) + if (i <= 0 or i > self.cols) then return vector.zero(self.rows) end + local vals = {} + for j = 1, self.rows do vals[j] = self:get(j, i) end + return vector.new(self.rows, vals) +end + +function matrix:set(i, j, value) + self.values[(i-1) * self.cols + j] = value + return self +end + +function matrix.__add(m1, m2) + if (m1.rows ~= m2.rows or m1.cols ~= m2.cols) then return m1 end + + local m = m1:clone() + for i = 1, #(m.values) do + m.values[i] = m.values[i] + m2.values[i] + end + + return m +end + +function matrix.__unm(m) + local n = matrix.new(m.rows, m.cols) + for i = 1, #(n.values) do + n.values[i] = -m.values[i] + end + return n +end + +function matrix.__sub(m1, m2) + if (m1.rows ~= m2.rows or m1.cols ~= m2.cols) then return m1 end + + local m = matrix.new(m1.rows, m1.cols) + for i = 1, #(m.values) do + m.values[i] = m1.values[i] - m2.values[i] + end + + return m +end + +function matrix.mul_matrix(m1, m2) + if (type(m1) == "number") then return matrix.mul_scalar(m2, m1) end + if (type(m2) == "number") then return matrix.mul_scalar(m1, m2) end + if (m1.cols ~= m2.rows) then return m1 end + + local m = matrix.new(m1.rows, m2.cols) + for i = 1, m1.rows do + for j = 1, m2.cols do + local sum = 0 + for k = 1, m1.cols do + sum = sum + m1:get(i, k) * m2:get(k, j) + end + m:set(i, j, sum) + end + end + + return m +end + +function matrix.mul_vector(m, v) + if (m.cols ~= v.size) then return v end + + local vals = {} + for i = 1, m.rows do + local sum = 0 + for j = 1, m.cols do + sum = sum + m:get(i, j) * v:get(j) + end + vals[i] = sum + end + + return vector.new(m.rows, vals) +end + +function matrix.mul_scalar(m, s) + local r = matrix.new(m.rows, m.cols) + for i = 1, #(r.values) do + r.values[i] = s * m.values[i] + end + return r +end + +function matrix.__mul(m, o) + if (getmetatable(o) == matrix) then return matrix.mul_matrix(m, o) + elseif (getmetatable(o) == vector) then return matrix.mul_vector(m, o) + elseif (type(o) == "number") then return matrix.mul_scalar(m, o) end + return m +end + +function matrix.__eq(m1, m2) + if (m1.rows ~= m2.rows or m1.cols ~= m2.cols) then return false end + + for i = 1, #(m1.values) do + if (m1.values[i] ~= m2.values[i]) then return false end + end + + return true +end diff --git a/quaternion.lua b/quaternion.lua new file mode 100644 index 0000000000000000000000000000000000000000..98ee5fdb712f70171af8502e0018b107c436cfd8 --- /dev/null +++ b/quaternion.lua @@ -0,0 +1,117 @@ +require("kara3d.vector") +require("kara3d.matrix") + +quaternion = {class = "quaternion"} +quaternion.__index = quaternion + +function quaternion.new(a, b, c, d) + local q = {x = a or 0, y = b or 0, z = c or 0, w = d or 1} + setmetatable(q, quaternion) + return q +end + +function quaternion:inv() + return quaternion.new(-self.x, -self.y, -self.z, -self.w) +end + +function quaternion:tostring() + return self.x .. ", " .. self.y .. ", " .. self.z .. ", " .. self.w +end + +function quaternion:set(x, y, z, w) + if (type(x) == "table" and x.class == "quaternion") then + self.x = x.x + self.y = x.y + self.z = x.z + self.w = x.w + else + self.x = x or 0 + self.y = y or 0 + self.z = z or 0 + self.w = w or 1 + end + return self +end + +function quaternion.mul_quaternion(q1, q2) + local b, c, d, a = q1:unpack() + local f, g, h, e = q2:unpack() + return quaternion.new(a*e - b*f - c*g - d*h, + a*f + b*e + c*h - d*g, + a*g + c*e + d*f - b*h, + a*h + d*e + b*g - c*f) +end + +function quaternion.mul_vector(q, v) + local v3 = vector.new(3, v.values) + local u1 = vector.new(3, {q.x, q.y, q.z}) + local u2 = vector.new(3, {-q.x, -q.y, -q.z}) + local d = vector.dot(u1, v3) + local c = vector.cross(u1, v3) + return 2*d * u1 + (q.w*q.w - u1:length2()) * v3 + 2*q.w * c +end + +function quaternion.from_to_rotation(from, to) + local v0 = from:normalized() + local v1 = to:normalized() + local d = v0:dot(v1) + + if (math.abs(d) < 0.001) then return quaternion.identity() end + + return quaternion.axis_angle(vector.cross(v0, v1):normalize(), math.acos(d)) +end + +function quaternion.axis_angle(axis, angle) + angle = angle or axis:length() + + local naxis = axis:normalized() + local hcos = math.cos(0.5 * angle) + local hsin = math.sin(0.5 * angle) + + return quaternion.new(naxis:x() * hsin, naxis:y() * hsin, naxis:z() * hsin, hcos) +end + +function quaternion:unpack() + return self.x, self.y, self.z, self.w +end + +function quaternion:normalize() + local len = self:length() + if len ~= 0 and len ~= 1 then + self.x = self.x / len + self.y = self.y / len + self.z = self.z / len + self.w = self.w / len + end + return self +end + +function quaternion:normalized() + local q = self:clone() + q:normalize() + return q +end + +function quaternion:clone() + return quaternion.new(self.x, self.y, self.z, self.w) +end + +function quaternion:length2() + return self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w +end + +function quaternion:length() + return math.sqrt(self:length2()) +end + +function quaternion.identity() return quaternion.new() end + +function quaternion:to_matrix() + local x, y, z, w = self:unpack() + local m = matrix.new(4, 4, + {1 - 2*y*y - 2*z*z, 2*x*y - 2*w*z, 2*w*y + 2*x*z, 0, + 2*w*z + 2*x*y, 1 - 2*x*x - 2*z*z, 2*y*z - 2*w*x, 0, + 2*x*z - 2*w*y, 2*w*x + 2*y*z, 1 - 2*x*x - 2*y*y, 0, + 0, 0, 0, 1}) + return m +end diff --git a/shape.lua b/shape.lua new file mode 100644 index 0000000000000000000000000000000000000000..ef54309137c191e6bc12ef948b7c65d956884b6c --- /dev/null +++ b/shape.lua @@ -0,0 +1,87 @@ +require("kara3d.vector") +require("kara3d.matrix") +require("kara3d.quaternion") +require("kara3d.transform") +require("utils") + +shape = {class = "shape"} +shape.__index = shape + +face = {class = "face"} +face.__index = face + +function face.new(v, n, t) + local f = {vertices = v or {}, normal = n or -vector.cardinal(3, 3), tags = t} + setmetatable(f, face) + return f +end + +function shape.new(t, f) + local s = {transform = t, faces = f or {}} + setmetatable(s, shape) + return s +end + +function shape:clone() + return shape.new(self.transform, table.copy(self.faces)) +end + +function shape:draw(subs, line, tags, cull) + local l = table.copy(line) + local p = self.transform:position(true) + local r = self.transform:rotation(true) + local m = self.transform:matrix(true) + local c = vector.cardinal(4, 4) + local return_str = nil + + for i = 1, #self.faces do + local face = self.faces[i] + local n = quaternion.mul_vector(r, face.normal) + + if not (cull and n:z() <= 0) then + local vs = face.vertices + + local v = matrix.mul_vector(m, vs[#vs] + c) - p + local str = (tags and "{" .. tags .. "}" or "") + .. (face.tags and "{" .. face.tags .. "}" or "") + .. "{\\an7\\pos(" .. p:x() .. ", " .. p:y() .. ")\\p1}" + .. "m " .. v:x() .. " " .. v:y() .. " " + + for i = 1, #vs do + v = matrix.mul_vector(m, vs[i] + c) - p + str = str .. "l " .. v:x() .. " " .. v:y() .. " " + end + + l.text = str + l.effect = "fx" + subs.append(l) + end + end + + return "" +end + +--[[ + 1________5 + /| /| + 2/__|____/6 | Y + | 3|_ _|_ _|7 |__ X + | / | / / +4|/______|/8 Z +]]-- +function shape.cube(t, size) + local hs = size / 2 + local vs = {vector.new(3, {-hs, -hs, -hs}), vector.new(3, {-hs, -hs, hs}), + vector.new(3, {-hs, hs, -hs}), vector.new(3, {-hs, hs, hs}), + vector.new(3, { hs, -hs, -hs}), vector.new(3, { hs, -hs, hs}), + vector.new(3, { hs, hs, -hs}), vector.new(3, { hs, hs, hs})} + + local left = face.new({vs[1], vs[2], vs[4], vs[3]}, -vector.cardinal(3, 1)) + local right = face.new({vs[5], vs[6], vs[8], vs[7]}, vector.cardinal(3, 1)) + local bottom = face.new({vs[1], vs[2], vs[6], vs[5]}, -vector.cardinal(3, 2)) + local top = face.new({vs[3], vs[4], vs[8], vs[7]}, vector.cardinal(3, 2)) + local back = face.new({vs[1], vs[5], vs[7], vs[3]}, -vector.cardinal(3, 3)) + local front = face.new({vs[2], vs[6], vs[8], vs[4]}, vector.cardinal(3, 3)) + + return shape.new(t, {left, right, bottom, top, back, front}) +end diff --git a/transform.lua b/transform.lua new file mode 100644 index 0000000000000000000000000000000000000000..4abe68413737d6b88f4a53bd32c81c1165042329 --- /dev/null +++ b/transform.lua @@ -0,0 +1,77 @@ +require("kara3d.vector") +require("kara3d.matrix") +require("kara3d.quaternion") + +transform = {class = "transform"} +transform.__index = transform + +function transform.new(p, r, s, par) + p = p or vector.zero(3) + r = r or quaternion.identity() + s = s or vector.one(3) + local t = {pos = p, rot = r, scl = s, parent = par} + setmetatable(t, transform) + return t +end + +function transform:tostring() + return "(pos = (" .. self.pos:tostring() .. "), " + .. "rot = (" .. self.rot:tostring() .. "), " + .. "scl = (" .. self.scl:tostring() .. "), " + .. (self.parent and "has parent" or "no parent") .. ")" +end + +function transform:rotate(axis, angle) + self.rot = quaternion.axis_angle(axis, angle) * self.rot + return self +end + +function transform:position(global) + local p = self.pos:clone() + if (p.size > 4) then p:set_size(4) end + if (p.size < 4) then p:set_size(4) p:set(4, 1) end + if (global and self.parent) then + p = matrix.mul_vector(self.parent:matrix(true), p) + end + return p:set_size(3) +end + +function transform:rotation(global) + local q = self.rot:normalized() + if (global and self.parent) then + q = quaternion.mul_quaternion(self.parent:rotation(true), q):normalize() + end + return q +end + +function transform:scale(global) + local s = self.scl:clone() + if (global and self.parent) then + local m = self:matrix(true) + s:set(1, m:column(1):set_size(3):length()) + s:set(2, m:column(2):set_size(3):length()) + s:set(3, m:column(3):set_size(3):length()) + end + return s +end + +function transform:matrix(global) + local translation = matrix.new(4, 4, + {1, 0, 0, self.pos:get(1), + 0, 1, 0, self.pos:get(2), + 0, 0, 1, self.pos:get(3), + 0, 0, 0, 1}) + local rotation = self.rot:normalized():to_matrix() + local scale = matrix.new(4, 4, + {self.scl:get(1), 0, 0, 0, + 0, self.scl:get(2), 0, 0, + 0, 0, self.scl:get(3), 0, + 0, 0, 0, 1}) + local m = translation * rotation * scale + + if (global and self.parent ~= nil) then + m = self.parent:matrix(true) * m + end + + return m +end diff --git a/vector.lua b/vector.lua new file mode 100644 index 0000000000000000000000000000000000000000..e77d9e4ce7b109449e2a75214d0f5943da43aa95 --- /dev/null +++ b/vector.lua @@ -0,0 +1,150 @@ +vector = {class = "vector"} +vector.__index = vector + +function vector.new(n, vals) + if (type(n) == "table") then vals = n n = #n end + vals = vals or {} + local v = {size = n or 3, values = {}} + for i = 1, v.size do + v.values[i] = i <= #vals and vals[i] or 0 + end + setmetatable(v, vector) + return v +end + +-- operator overloading +function vector.__add(a, b) + local small = a.size < b.size and a or b + local big = a.size < b.size and b or a + local r = big:clone() + for i = 1, small.size do r:set(i, r:get(i) + small:get(i)) end + return r +end + +function vector.__unm(a) + local r = vector.new(a.size) + for i = 1, r.size do r:set(i, -(a:get(i))) end + return r +end + +function vector.__sub(a, b) + local oppb = -b + return a + oppb +end + +function vector.__mul(a, b) + if type(a) == "number" and type(b) ~= "number" then + local r = vector.new(b.size) + for i = 1, r.size do r:set(i, a * b:get(i)) end + return r + elseif type(a) ~= "number" and type(b) == "number" then + local r = vector.new(a.size) + for i = 1, r.size do r:set(i, a:get(i) * b) end + return r + elseif type(a) ~= "number" and type(b) ~= "number" then + local small = a.size < b.size and a or b + local big = a.size < b.size and b or a + local r = vector.new(big.size) + for i = 1, small.size do r:set(i, small:get(i) * big:get(i)) end + return r + else + return a * b + end +end + +function vector.__eq(a, b) + if (a.size ~= b.size) then return false end + for i = 1, a.size do if (a:get(i) ~= b:get(i)) then return false end end + return true +end + +-- actual functions +function vector:tostring() + local str = "" + for i = 1, self.size - 1 do + str = str .. self:get(i) .. ", " + end + return str .. self:get(self.size) +end + +function vector:clone() return vector.new(self.size, self.values) end + +function vector:length2() + local sum = 0 + for i = 1, self.size do sum = sum + self:get(i) * self:get(i) end + return sum +end + +function vector:length() return math.sqrt(self:length2()) end + +function vector:is_unit() return self:length2() == 1 end + +function vector:normalize() + local len = self:length() + if len == 0 or len == 1 then return self end + for i = 1, self.size do self:set(i, self:get(i) / len) end + return self +end + +function vector:normalized() return self:clone():normalize() end + +function vector.project_vector(from, to) return (vector.dot(from, to) / to:length2()) * to end +function vector.project_plane(from, normal) return from - vector.project_vector(from, normal) end + +function vector.dot(a, b) + local min_size = math.min(a.size, b.size) + local sum = 0 + for i = 1, min_size do sum = sum + a.values[i] * b.values[i] end + return sum +end + +function vector.distance2(a, b) return (a - b):length2() end +function vector.distance(a, b) return (a - b):length() end + +function vector.angle(from, to) return math.acos(vector.dot(from, to) / (from:length() * to:length())) end +function vector.lerp(from, to, t) return t * from + (1 - t) * to end + +function vector:set_values(vals) + vals = vals or {} + for i = 1, #vals do self.values[i] = vals[i] end + for i = #vals + 1, self.size do self.values[i] = nil end + self.size = #vals + return self +end + +function vector:set_size(n) + if (self.size == n) then return self end + for i = n + 1, self.size do self:set(i, nil) end + for i = self.size + 1, n do self:set(i, 0) end + self.size = n + return self +end + +function vector.cross(a, b) + return vector.new(3, {a.values[2] * b.values[3] - a.values[3] * b.values[2], + a.values[3] * b.values[1] - a.values[1] * b.values[3], + a.values[1] * b.values[2] - a.values[2] * b.values[1]}) +end + +function vector.zero(size) return vector.new(size) end +function vector.one(size) + local vals = {} + for i = 1, size do vals[i] = 1 end + return vector.new(size, vals) +end +function vector.cardinal(size, index) + local vals = {} + for i = 1, size do vals[i] = i == index and 1 or 0 end + return vector.new(size, vals) +end + +function vector:get(index) return self.values[index] end +function vector:set(index, value) + self.values[index] = value + return self +end + +function vector:x() return self.values[1] end +function vector:y() return self.values[2] end +function vector:z() return self.values[3] end +function vector:w() return self.values[4] end