diff --git a/_math.lua b/_math.lua index a9278f4..08b8d16 100644 --- a/_math.lua +++ b/_math.lua @@ -2,26 +2,64 @@ local ffi = require 'ffi' local sin = math.sin local cos = math.cos +local sqrt = math.sqrt +local abs = math.abs local mat4 +local vec3 +local scalar -local newmat4 = function(_t) - -- newByteData is zero-initialized - local data = love.data.newByteData(16 * 4) - local m = ffi.cast('float*', data:getFFIPointer()) - value = { - data = data, - m = m, - } - setmetatable(value, mat4) - return value -end +scalar = { + near_equal = function(s1, s2, epsilon) + local d = abs(s1 - s2) + return d <= epsilon + end, +} + +setmetatable(scalar, scalar) mat4 = { - __call = newmat4, + __call = function(_t) + -- newByteData is zero-initialized + local data = love.data.newByteData(16 * 4) + local m = ffi.cast('float*', data:getFFIPointer()) + value = { + data = data, + m = m, + } + setmetatable(value, mat4) + return value + end, + + set = function(m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33) + local M = mat4() + M.m[0 * 4 + 0] = m00 + M.m[0 * 4 + 1] = m01 + M.m[0 * 4 + 2] = m02 + M.m[0 * 4 + 3] = m03 + + M.m[1 * 4 + 0] = m10 + M.m[1 * 4 + 1] = m11 + M.m[1 * 4 + 2] = m12 + M.m[1 * 4 + 3] = m13 + + M.m[2 * 4 + 0] = m20 + M.m[2 * 4 + 1] = m21 + M.m[2 * 4 + 2] = m22 + M.m[2 * 4 + 3] = m23 + + M.m[3 * 4 + 0] = m30 + M.m[3 * 4 + 1] = m31 + M.m[3 * 4 + 2] = m32 + M.m[3 * 4 + 3] = m33 + return M + end, identity = function() - local M = newmat4() + local M = mat4() M.m[0 * 4 + 0] = 1.0 --M.m[0 * 4 + 1] = 0.0 --M.m[0 * 4 + 2] = 0.0 @@ -45,7 +83,7 @@ mat4 = { end, translation = function(x, y, z) - local M = newmat4() + local M = mat4() M.m[0 * 4 + 0] = 1.0 --M.m[0 * 4 + 1] = 0.0 --M.m[0 * 4 + 2] = 0.0 @@ -72,7 +110,7 @@ mat4 = { local sin_angle = sin(angle) local cos_angle = cos(angle) - local M = newmat4() + local M = mat4() M.m[0 * 4 + 0] = 1.0 --M.m[0 * 4 + 1] = 0.0 --M.m[0 * 4 + 2] = 0.0 @@ -99,7 +137,7 @@ mat4 = { local sin_angle = sin(angle) local cos_angle = cos(angle) - local M = newmat4() + local M = mat4() M.m[0 * 4 + 0] = cos_angle --M.m[0 * 4 + 1] = 0.0 M.m[0 * 4 + 2] = -sin_angle @@ -126,7 +164,7 @@ mat4 = { local sin_angle = sin(angle) local cos_angle = cos(angle) - local M = newmat4() + local M = mat4() M.m[0 * 4 + 0] = cos_angle M.m[0 * 4 + 1] = sin_angle --M.m[0 * 4 + 2] = 0.0 @@ -149,13 +187,313 @@ mat4 = { return M end, - dot = function(a, b) - print("dot" .. tostring(a) .. tostring(b)) + transpose = function(M) + local MT = mat4() + --MT.m[0 * 4 + 0] = M.m[0 * 4 + 0] + MT.m[0 * 4 + 1] = M.m[1 * 4 + 0] + MT.m[0 * 4 + 2] = M.m[2 * 4 + 0] + MT.m[0 * 4 + 3] = M.m[3 * 4 + 0] + + MT.m[1 * 4 + 0] = M.m[0 * 4 + 1] + --MT.m[1 * 4 + 1] = M.m[1 * 4 + 1] + MT.m[1 * 4 + 2] = M.m[2 * 4 + 1] + MT.m[1 * 4 + 3] = M.m[3 * 4 + 1] + + MT.m[2 * 4 + 0] = M.m[0 * 4 + 2] + MT.m[2 * 4 + 1] = M.m[1 * 4 + 2] + --MT.m[2 * 4 + 2] = M.m[2 * 4 + 2] + MT.m[2 * 4 + 3] = M.m[3 * 4 + 2] + + MT.m[3 * 4 + 0] = M.m[0 * 4 + 3] + MT.m[3 * 4 + 1] = M.m[1 * 4 + 3] + MT.m[3 * 4 + 2] = M.m[2 * 4 + 3] + --MT.m[3 * 4 + 3] = M.m[3 * 4 + 3] + return MT + end, + + look_to_lh = function(eye_position, eye_direction, up_direction) + assert(not vec3.equal(eye_direction, vec3._zero)) + assert(not vec3.isinfinite(eye_direction)) + assert(not vec3.equal(up_direction, vec3._zero)) + assert(not vec3.isinfinite(up_direction)) + + local r2 = vec3.normalize(eye_direction) + local r0 = vec3.cross(up_direction, r2) + r0 = vec3.normalize(r0) + + local r1 = vec3.cross(r2, r0) + + local neg_eye_position = vec3.negate(eye_position) + + local d0 = vec3.dot(r0, neg_eye_position) + local d1 = vec3.dot(r1, neg_eye_position) + local d2 = vec3.dot(r2, neg_eye_position) + + local M = mat4() + M.m[0 * 4 + 0] = r0.f[0] + M.m[1 * 4 + 0] = r0.f[1] + M.m[2 * 4 + 0] = r0.f[2] + M.m[3 * 4 + 0] = d0 + + M.m[0 * 4 + 1] = r1.f[0] + M.m[1 * 4 + 1] = r1.f[1] + M.m[2 * 4 + 1] = r1.f[2] + M.m[3 * 4 + 1] = d1 + + M.m[0 * 4 + 2] = r2.f[0] + M.m[1 * 4 + 2] = r2.f[1] + M.m[2 * 4 + 2] = r2.f[2] + M.m[3 * 4 + 2] = d2 + + --M.m[0 * 4 + 3] = 0 + --M.m[1 * 4 + 3] = 0 + --M.m[2 * 4 + 3] = 0 + M.m[3 * 4 + 3] = 1 + return M + end, + + look_at_lh = function(eye_position, focus_position, up_direction) + local eye_direction = vec3.subtract(focus_position, eye_position) + return mat4.look_to_lh(eye_position, eye_direction, up_direction) + end, + + look_at_rh = function(eye_position, focus_position, up_direction) + local neg_eye_direction = vec3.subtract(eye_position, focus_position) + return mat4.look_to_lh(eye_position, neg_eye_direction, up_direction) + end, + + perspective_rh = function(view_width, view_height, near_z, far_z) + assert(near_z > 0.0 and far_z > 0.0) + assert(not scalar.near_equal(view_width, 0.0, 0.00001)) + assert(not scalar.near_equal(view_height, 0.0, 0.00001)) + assert(not scalar.near_equal(far_z, near_z, 0.00001)) + + local two_near_z = near_z + near_z; + local f_range = far_z / (near_z - far_z); + + local M = mat4() + M.m[0 * 4 + 0] = two_near_z / view_width + --M.m[0 * 4 + 1] = 0.0 + --M.m[0 * 4 + 2] = 0.0 + --M.m[0 * 4 + 3] = 0.0 + + --M.m[1 * 4 + 0] = 0.0 + M.m[1 * 4 + 1] = two_near_z / view_height + --M.m[1 * 4 + 2] = 0.0 + --M.m[1 * 4 + 3] = 0.0 + + --M.m[2 * 4 + 0] = 0.0 + --M.m[2 * 4 + 1] = 0.0 + M.m[2 * 4 + 2] = f_range + M.m[2 * 4 + 3] = -1.0 + + --M.m[3 * 4 + 0] = 0.0 + --M.m[3 * 4 + 1] = 0.0 + M.m[3 * 4 + 2] = f_range * near_z + --M.m[3 * 4 + 3] = 0.0 + return M + end, + + near_equal = function(M1, M2, epsilon) + local d00 = abs(M1.m[0 * 4 + 0] - M2.m[0 * 4 + 0]) + local d01 = abs(M1.m[0 * 4 + 1] - M2.m[0 * 4 + 1]) + local d02 = abs(M1.m[0 * 4 + 2] - M2.m[0 * 4 + 2]) + local d03 = abs(M1.m[0 * 4 + 3] - M2.m[0 * 4 + 3]) + + local d10 = abs(M1.m[1 * 4 + 0] - M2.m[1 * 4 + 0]) + local d11 = abs(M1.m[1 * 4 + 1] - M2.m[1 * 4 + 1]) + local d12 = abs(M1.m[1 * 4 + 2] - M2.m[1 * 4 + 2]) + local d13 = abs(M1.m[1 * 4 + 3] - M2.m[1 * 4 + 3]) + + local d20 = abs(M1.m[2 * 4 + 0] - M2.m[2 * 4 + 0]) + local d21 = abs(M1.m[2 * 4 + 1] - M2.m[2 * 4 + 1]) + local d22 = abs(M1.m[2 * 4 + 2] - M2.m[2 * 4 + 2]) + local d23 = abs(M1.m[2 * 4 + 3] - M2.m[2 * 4 + 3]) + + local d30 = abs(M1.m[3 * 4 + 0] - M2.m[3 * 4 + 0]) + local d31 = abs(M1.m[3 * 4 + 1] - M2.m[3 * 4 + 1]) + local d32 = abs(M1.m[3 * 4 + 2] - M2.m[3 * 4 + 2]) + local d33 = abs(M1.m[3 * 4 + 3] - M2.m[3 * 4 + 3]) + + return ( + (d00 <= epsilon) and (d01 <= epsilon) and (d02 <= epsilon) and (d03 <= epsilon) and + (d10 <= epsilon) and (d11 <= epsilon) and (d12 <= epsilon) and (d13 <= epsilon) and + (d20 <= epsilon) and (d21 <= epsilon) and (d22 <= epsilon) and (d23 <= epsilon) and + (d30 <= epsilon) and (d31 <= epsilon) and (d32 <= epsilon) and (d33 <= epsilon) + ) + end, + + print = function(M) + for i = 0, 15 do + io.write(tostring(M.m[i])) + io.write(" ") + if i % 4 == 3 then + io.write("\n") + end + end end } setmetatable(mat4, mat4) -return { - mat4 = mat4 +vec3 = { + __call = function(_t, x, y, z) + -- newByteData is zero-initialized + local data = love.data.newByteData(3 * 4) + local f = ffi.cast('float*', data:getFFIPointer()) + value = { + data = data, + f = f, + } + f[0] = x or 0 + f[1] = y or 0 + f[2] = z or 0 + setmetatable(value, vec3) + return value + end, + + dot = function(v1, v2) + local value = ( + v1.f[0] * v2.f[0] + + v1.f[1] * v2.f[1] + + v1.f[2] * v2.f[2] + ) + return value + end, + + length_sq = function(v) + return vec3.dot(v, v) + end, + + reciprocal_length = function(v) + local result + result = vec3.length_sq(v) + result = 1.0 / sqrt(result) + return result + end, + + length = function(v) + local result + result = vec3.length_sq(v) + result = sqrt(result) + return result + end, + + add = function(v1, v2) + local result = vec3( + v1.f[0] + v2.f[0], + v1.f[1] + v2.f[1], + v1.f[2] + v2.f[2] + ) + return result + end, + + subtract = function(v1, v2) + local result = vec3( + v1.f[0] - v2.f[0], + v1.f[1] - v2.f[1], + v1.f[2] - v2.f[2] + ) + return result + end, + + multiply = function(v1, v2) + local result = vec3( + v1.f[0] * v2.f[0], + v1.f[1] * v2.f[1], + v1.f[2] * v2.f[2] + ) + return result + end, + + normalize = function(v) + local length = vec3.reciprocal_length(v) + local result = vec3( + v.f[0] * length, + v.f[1] * length, + v.f[2] * length + ) + return result + end, + + cross = function(v1, v2) + local result = vec3( + (v1.f[1] * v2.f[2]) - (v1.f[2] * v2.f[1]), + (v1.f[2] * v2.f[0]) - (v1.f[0] * v2.f[2]), + (v1.f[0] * v2.f[1]) - (v1.f[1] * v2.f[0]) + ) + return result + end, + + negate = function(v) + local result = vec3( + -v.f[0], + -v.f[1], + -v.f[2] + ) + return result + end, + + equal = function(v1, v2) + return ( + (v1.f[0] == v2.f[0]) and + (v1.f[1] == v2.f[1]) and + (v1.f[2] == v2.f[2]) + ) + end, + + near_equal = function(v1, v2, epsilon) + local dx = abs(v1.f[0] - v2.f[0]) + local dy = abs(v1.f[1] - v2.f[1]) + local dz = abs(v1.f[2] - v2.f[2]) + return (dx <= epsilon) and (dy <= epsilon) and (dz <= epsilon) + end, + + isinfinite = function(v) + return ( + (v.f[0] == -math.huge or v.f[0] == math.huge) or + (v.f[1] == -math.huge or v.f[1] == math.huge) or + (v.f[2] == -math.huge or v.f[2] == math.huge) + ) + end, + + print = function(v) + print(tostring(v.f[0]) .. " " .. tostring(v.f[1]) .. " " .. tostring(v.f[2])) + end, +} + +setmetatable(vec3, vec3) +vec3._zero = vec3(0, 0, 0) + +---------------------------------------------------------------------- +-- tests +---------------------------------------------------------------------- + +assert(vec3.dot(vec3(1, 3, -5), vec3(4, -2, -1)) == 3) +assert(vec3.dot(vec3(1, 3, -5), vec3(1, 3, -5)) == 35) +assert(vec3.length_sq(vec3(1, 3, -5)) == 35) +assert(vec3.equal(vec3.multiply(vec3(1, 3, -5), vec3(2, 3, 4)), vec3(2, 9, -20))) +assert(vec3.near_equal(vec3.normalize(vec3(1, 3, -5)), vec3(0.16903, 0.50709, -0.84515), 0.0001)) +assert(vec3.equal(vec3.cross(vec3(1, 2, 3), vec3(4, 5, 6)), vec3(-3, 6, -3))) + +assert(mat4.near_equal(mat4.look_to_lh(vec3(1, 2, 3), + vec3(5, 6, 7), + vec3(9, 10, 11)), + mat4.set( 0.408249, 0.778499, 0.476731, 0.000000, + -0.816496, 0.077850, 0.572078, 0.000000, + 0.408248, -0.622799, 0.667424, 0.000000, + -0.000001, 0.934199, -3.623158, 1.000000), + 0.00001)) + +assert(mat4.near_equal(mat4.perspective_rh(2, 3, 4, 5), + mat4.set(4.000000, 0.000000, 0.000000, 0.000000, + 0.000000, 2.666667, 0.000000, 0.000000, + 0.000000, 0.000000, -5.000000, -1.000000, + 0.000000, 0.000000, -20.000000, 0.000000), + 0.00001)) + +return { + scalar = scalar, + mat4 = mat4, + vec3 = vec3, } diff --git a/main.lua b/main.lua index badddac..265e3de 100644 --- a/main.lua +++ b/main.lua @@ -3,6 +3,9 @@ local rotation local texture local ffi = require 'ffi' +local _math = require '_math' +local mat4 = _math.mat4 +local vec3 = _math.vec3 local pixelcode = [[ #pragma language glsl3 @@ -55,27 +58,6 @@ local vertexformat = { { name = 'VertexTexture', format = 'floatvec3', location = 2 }, } -function look_at() - local m = { - 1.000000, 0.000000, 0.000000, 0.000000, - 0.000000, 1.000000, 0.000000, 0.000000, - 0.000000, 0.000000, 1.000000, -2.000000, - 0.000000, 0.000000, 0.000000, 1.000000, - } - - return m; -end - -function projection() - local m = { - 1.000000, 0.000000, 0.000000, 0.000000, - 0.000000, 1.000000, 0.000000, 0.000000, - 0.000000, 0.000000, -1.002002, -0.200200, - 0.000000, 0.000000, -1.000000, 0.000000, - } - return m -end - function love.load(args) love.window.setMode(1024, 1024, {depth=true}) @@ -122,59 +104,19 @@ end local rotation = 0.0 -function rotate_x(t) - local cos = math.cos - local sin = math.sin - return { - 1, 0, 0, 0, - 0, cos(t), -sin(t), 0, - 0, sin(t), cos(t), 0, - 0, 0, 0, 1 - } -end - -function rotate_y(t) - local cos = math.cos - local sin = math.sin - return { - cos(t), 0, sin(t), 0, - 0, 1, 0, 0, - -sin(t), 0, cos(t), 0, - 0, 0, 0, 1 - } -end - -function rotate_z(t) - local cos = math.cos - local sin = math.sin - return { - cos(t), -sin(t), 0, 0, - sin(t), cos(t), 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - } -end - -function print_matrix(ptr) - for i=0,15 do - io.write(tostring(ptr[i])) - io.write(" ") - if i % 4 == 3 then - io.write("\n") - end - end -end - - -local math = require '_math' -local mat4 = math.mat4 - function love.draw() local radius = 100 local mx, my = love.mouse.getPosition() - shader:send("projection", projection(1, 1)) - shader:send("view", "column", mat4.translation(0, 0, -2).data) + width, height = love.graphics.getDimensions() + shader:send("projection", "column", mat4.perspective_rh(width / width * 0.25, + height / width * 0.25, + 0.1, + 1000.0).data) + shader:send("view", "column", mat4.look_at_rh(vec3(0, -2, 0), + vec3(0, 0, 0), + vec3(0, 0, 1)).data) + shader:send("model", "column", mat4.rotation_x(rotation).data) shader:send("model2", "column", mat4.rotation_z(rotation * 0.5).data) shader:send("texture_sampler", texture)