From 13c9711b883931845a11c591d1880ce00827e1a3 Mon Sep 17 00:00:00 2001
From: ultrakatiz <ultrakatiz@gmail.com>
Date: Fri, 20 May 2022 00:11:17 +0200
Subject: [PATCH] shapes can now render with a camera

---
 all.lua       |  1 +
 camera.lua    | 35 +++++++++++++++++++++++++++++++++++
 shape.lua     | 21 +++++++++++++++------
 transform.lua | 24 +++++++++++++++++++++++-
 4 files changed, 74 insertions(+), 7 deletions(-)
 create mode 100644 camera.lua

diff --git a/all.lua b/all.lua
index 3834ea8..f8b0e4f 100644
--- a/all.lua
+++ b/all.lua
@@ -3,3 +3,4 @@ require("kara3d.matrix")
 require("kara3d.quaternion")
 require("kara3d.transform")
 require("kara3d.shape")
+require("kara3d.camera")
diff --git a/camera.lua b/camera.lua
new file mode 100644
index 0000000..d5b2108
--- /dev/null
+++ b/camera.lua
@@ -0,0 +1,35 @@
+require("kara3d.transform")
+
+-- ********** DEFINITION AND GENERIC FUNCTIONS **********
+
+camera = {class = "camera"}
+camera.__index = camera
+
+-- Creates a camera at transform t with aspect ratio a, horizontal fov f,
+-- near plane distance nd and far plane distance fd
+function camera.new(t, f, nd, fd)
+  local c = {transform = t, fov = f or 80 / 180 * math.pi,
+    near = nd or 1, far = fd or 1000000000}
+  setmetatable(c, camera)
+  return c
+end
+
+-- Clones this camera
+function camera:clone()
+  return camera.new(self.transform, self.fov, self.near, self.far)
+end
+
+-- ********** INSTANCE FUNCTIONS **********
+
+-- Returns this camera's projection * view matrix
+function camera:matrix()
+  local s = 1 / math.tan(self.fov / 2)
+  local n, f = self.near, self.far
+  local xres, yres, _, _ = aegisub.video_size()
+  local m = matrix.new(4, 4,
+    {s, 0,             0,             0,
+     0, s,             0,             0,
+     0, 0, (n+f) / (n-f), 2*n*f / (n-f),
+     0, 0,      1 / xres,             0})
+  return m * self.transform:inv_matrix(true)
+end
diff --git a/shape.lua b/shape.lua
index 803e4f0..22aadec 100644
--- a/shape.lua
+++ b/shape.lua
@@ -2,6 +2,7 @@ require("kara3d.vector")
 require("kara3d.matrix")
 require("kara3d.quaternion")
 require("kara3d.transform")
+require("kara3d.camera")
 require("utils")
 
 -- ********** DEFINITION AND GENERIC FUNCTIONS **********
@@ -36,37 +37,45 @@ end
 -- Generates a line for each face of this shape, with the indicated tags
 -- The boolean cull determines if the faces facing away from the screen are
 -- drawn or not
-function shape:draw(subs, line, tags, cull)
+function shape:draw(subs, line, tags, cull, cam)
   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 layer = line.layer
 
+  local pv = cam and cam:matrix() or matrix.identity(4, 4)
+  local cam_inv_rot = cam and cam.transform:rotation(true):inv() or quaternion.identity()
+  local xres, yres, _, _ = aegisub.video_size()
+
+  local m = pv * self.transform:matrix(true)
+
   for i = 1, #self.faces do
     local face = self.faces[i]
     local n = quaternion.mul_vector(r, face.normal)
+    n = quaternion.mul_vector(cam_inv_rot, n)
 
     if not (cull and n:z() > 0) then
       local vs = face.vertices
 
-      local v = matrix.mul_vector(m, vs[#vs] + c) - p
+      local v = m * (vs[#vs] + c)
+      v = (1 / v:w()) * v
       local sum = vector.zero(4)
       local str = (tags and "{" .. tags .. "}" or "")
         .. (face.tags and "{" .. face.tags .. "}" or "")
-        .. "{\\an7\\pos(" .. p:x() .. ", " .. p:y() .. ")\\p1}"
+        .. "{\\an7\\pos(" .. (xres / 2) .. ", " .. (yres / 2) .. ")\\p1}"
         .. "m " .. v:x() .. " " .. v:y() .. " "
 
       for i = 1, #vs do
-        v = matrix.mul_vector(m, vs[i] + c) - p
+        v = m * (vs[i] + c)
         sum = sum + v
+        v = (1 / v:w()) * v
         str = str .. "l " .. v:x() .. " " .. v:y() .. " "
       end
 
       l.text = str
       -- layers must be between 0 and 9999999999
-      l.layer = 500000000 + math.floor(-(p + sum):z() / #vs)
+      l.layer = 500000000 + math.floor((p + sum):z() / #vs)
       l.effect = "fx"
       subs.append(l)
     end
diff --git a/transform.lua b/transform.lua
index e755162..b141d8f 100644
--- a/transform.lua
+++ b/transform.lua
@@ -81,9 +81,31 @@ function transform:matrix(global)
                    0,               0,               0, 1})
   local m = translation * rotation * scale
 
-  if (global and self.parent ~= nil) then
+  if (global and self.parent) then
     m = self.parent:matrix(true) * m
   end
 
   return m
 end
+
+-- Returns the inverse of the transformation matrix of this transform
+function transform:inv_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():inv():to_matrix()
+  local scale = matrix.new(4, 4,
+    {1/self.scl:get(1),                 0,                 0, 0,
+                     0, 1/self.scl:get(2),                 0, 0,
+                     0,                 0, 1/self.scl:get(3), 0,
+                     0,                 0,                 0, 1})
+  local m = scale * rotation * translation
+
+  if (global and self.parent) then
+    m = m * self.parent:inv_matrix(true)
+  end
+
+  return m
+end
-- 
GitLab