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