diff --git a/modules/color.lua b/modules/color.lua index 51a6a1e..7456fee 100644 --- a/modules/color.lua +++ b/modules/color.lua @@ -1,9 +1,10 @@ --- Color utilities -- @module color -local modules = (...):gsub('%.[^%.]+$', '') .. "." -local utils = require(modules .. "utils") -local color = {} +local modules = (...):gsub('%.[^%.]+$', '') .. "." +local utils = require(modules .. "utils") +local color = {} +local color_mt = {} local function new(r, g, b, a) return setmetatable({ r, g, b, a }, color) @@ -258,7 +259,6 @@ function color.to_string(a) return string.format("[ %3.0f, %3.0f, %3.0f, %3.0f ]", a[1], a[2], a[3], a[4]) end -local color_mt = {} color_mt.__index = color color_mt.__tostring = color.to_string diff --git a/modules/mat4.lua b/modules/mat4.lua index 1c90fc7..0907870 100644 --- a/modules/mat4.lua +++ b/modules/mat4.lua @@ -11,6 +11,7 @@ local sin = math.sin local tan = math.tan local rad = math.rad local mat4 = {} +local mat4_mt = {} -- Private constructor. local function new(m) @@ -480,7 +481,11 @@ function mat4.unproject(win, view, projection, viewport) end function mat4.is_mat4(a) - if not type(a) == "table" and not type(a) == "cdata" then + if type(a) == "cdata" then + return ffi.istype("cpml_mat4", a) + end + + if type(a) ~= "table" then return false end @@ -623,7 +628,6 @@ function mat4.to_frustum(a, infinite) return frustum end -local mat4_mt = {} mat4_mt.__index = mat4 mat4_mt.__tostring = mat4.to_string diff --git a/modules/quat.lua b/modules/quat.lua index 92cd04f..2c497b8 100644 --- a/modules/quat.lua +++ b/modules/quat.lua @@ -17,6 +17,7 @@ local max = math.max local sqrt = math.sqrt local pi = math.pi local quat = {} +local quat_mt = {} -- Private constructor. local function new(x, y, z, w) @@ -25,9 +26,6 @@ local function new(x, y, z, w) return setmetatable(q, quat_mt) end -quat.unit = new(0, 0, 0, 1) -quat.zero = new(0, 0, 0, 0) - -- Statically allocate a temporary variable used in some of our functions. local tmp = new(0, 0, 0, 0) @@ -269,7 +267,7 @@ end function quat.lerp(out, a, b, s) return out :sub(b, a) - :mul(out, s) + :scale(out, s) :add(a, out) :normalize(out) end @@ -305,24 +303,6 @@ function quat.slerp(out, a, b, s) :add(tmp, out) end ---- Return the imaginary part of the quaternion as a vec3. --- @tparam vec3 out --- @tparam quat a --- @treturn vec3 out -function quat.imaginary(out, a) - out.x = a.x - out.y = a.y - out.z = a.z - return out -end - ---- Return the real part of a quaternion. --- @tparam quat a --- @treturn number real -function quat.real(a) - return a.w -end - --- Unpack a quaternion into form x,y,z,w. -- @tparam quat a -- @treturn number x @@ -355,7 +335,8 @@ function quat.to_angle_axis(a) return angle, vec3(x, y, z) end -function quat.to_vec3(out, a) +function quat.to_vec3(a) + local out = vec3() out.x = a.x out.y = a.y out.z = a.z @@ -373,11 +354,12 @@ end -- @param q object to be tested -- @treturn boolean function quat.is_quat(a) + if type(a) == "cdata" then + return ffi.istype("cpml_quat", a) + end + return - ( - type(a) == "table" or - type(a) == "cdata" - ) and + type(a) == "table" and type(a.x) == "number" and type(a.y) == "number" and type(a.z) == "number" and @@ -403,12 +385,11 @@ function quat.is_imaginary(a) return a.w == 0 end -local quat_mt = {} quat_mt.__index = quat quat_mt.__tostring = quat.to_string function quat_mt.__call(_, x, y, z, w) - return new(x, y, z, w) + return quat.new(x, y, z, w) end function quat_mt.__unm(a) @@ -435,7 +416,7 @@ end function quat_mt.__mul(a, b) assert(quat.is_quat(a), "__mul: Wrong argument type for left hand operant. ( expected)") - assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operant. ( or expected)") + assert(quat.is_quat(b) or vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type for right hand operant. ( or or expected)") if quat.is_quat(b) then return new():mul(a, b) @@ -450,7 +431,7 @@ end function quat_mt.__pow(a, n) assert(quat.is_quat(a), "__pow: Wrong argument type for left hand operant. ( expected)") - assert(type(b) == "number", "__pow: Wrong argument type for right hand operant. ( expected)") + assert(type(n) == "number", "__pow: Wrong argument type for right hand operant. ( expected)") return new():pow(a, n) end @@ -458,4 +439,7 @@ if status then ffi.metatype(new, quat_mt) end +quat.unit = new(0, 0, 0, 1) +quat.zero = new(0, 0, 0, 0) + return setmetatable({}, quat_mt) diff --git a/modules/vec2.lua b/modules/vec2.lua index a8e6d2f..58bded1 100644 --- a/modules/vec2.lua +++ b/modules/vec2.lua @@ -1,11 +1,12 @@ --- A 2 component vector. -- @module vec2 -local atan2 = math.atan2 -local sqrt = math.sqrt -local cos = math.cos -local sin = math.sin -local vec2 = {} +local atan2 = math.atan2 +local sqrt = math.sqrt +local cos = math.cos +local sin = math.sin +local vec2 = {} +local vec2_mt = {} -- Private constructor. local function new(x, y) @@ -14,10 +15,6 @@ local function new(x, y) return setmetatable(v, vec2_mt) end -vec2.unit_x = new(1, 0) -vec2.unit_y = new(0, 1) -vec2.zero = new(0, 0) - -- Do the check to see if JIT is enabled. If so use the optimized FFI structs. local status, ffi if type(jit) == "table" and jit.status() then @@ -28,6 +25,10 @@ if type(jit) == "table" and jit.status() then end end +vec2.unit_x = new(1, 0) +vec2.unit_y = new(0, 1) +vec2.zero = new(0, 0) + --- The public constructor. -- @param x Can be of three types:
-- number x component @@ -222,11 +223,12 @@ end -- @param v the object to be tested -- @treturn boolean function vec2.is_vec2(a) + if type(a) == "cdata" then + return ffi.istype("cpml_vec2", a) + end + return - ( - type(a) == "table" or - type(a) == "cdata" - ) and + type(a) == "table" and type(a.x) == "number" and type(a.y) == "number" end @@ -253,7 +255,6 @@ function vec2.to_string(a) return string.format("(%+0.3f,%+0.3f)", a.x, a.y) end -local vec2_mt = {} vec2_mt.__index = vec2 vec2_mt.__tostring = vec2.to_string diff --git a/modules/vec3.lua b/modules/vec3.lua index c8be022..08586d6 100644 --- a/modules/vec3.lua +++ b/modules/vec3.lua @@ -1,10 +1,11 @@ --- A 3 component vector. -- @module vec3 -local sqrt = math.sqrt -local cos = math.cos -local sin = math.sin -local vec3 = {} +local sqrt = math.sqrt +local cos = math.cos +local sin = math.sin +local vec3 = {} +local vec3_mt = {} -- Private constructor. local function new(x, y, z) @@ -13,11 +14,6 @@ local function new(x, y, z) return setmetatable(v, vec3_mt) end -vec3.unit_x = new(1, 0, 0) -vec3.unit_y = new(0, 1, 0) -vec3.unit_z = new(0, 0, 1) -vec3.zero = new(0, 0, 0) - -- Do the check to see if JIT is enabled. If so use the optimized FFI structs. local status, ffi if type(jit) == "table" and jit.status() then @@ -28,6 +24,11 @@ if type(jit) == "table" and jit.status() then end end +vec3.unit_x = new(1, 0, 0) +vec3.unit_y = new(0, 1, 0) +vec3.unit_z = new(0, 0, 1) +vec3.zero = new(0, 0, 0) + -- Statically allocate a temporary variable used in some of our functions. local tmp = new(0, 0, 0) @@ -248,12 +249,12 @@ end -- @param v the object to be tested -- @treturn boolean function vec3.is_vec3(a) - return - ( - type(a) == "table" or - type(a) == "cdata" + if type(a) == "cdata" then + return ffi.istype("cpml_vec3", a) + end - ) and + return + type(a) == "table" and type(a.x) == "number" and type(a.y) == "number" and type(a.z) == "number" @@ -273,7 +274,6 @@ function vec3.to_string(a) return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z) end -local vec3_mt = {} vec3_mt.__index = vec3 vec3_mt.__tostring = vec3.to_string diff --git a/spec/mat4_spec.lua b/spec/mat4_spec.lua index cefc0b3..5f5bc9e 100644 --- a/spec/mat4_spec.lua +++ b/spec/mat4_spec.lua @@ -2,3 +2,30 @@ local mat4 = require "modules.mat4" describe("mat4:", function() end) + +--[[ + new + identity + from_angle_axis + from_direction + from_transform + from_ortho + from_perspective + from_hmd_perspective + clone + mul + mul_mat4x1 + invert + scale + rotate + translate + shear + look_at + transpose + project + unproject + is_mat4 + to_vec4s + to_quat + to_frustum +]] diff --git a/spec/quat_spec.lua b/spec/quat_spec.lua index 79abfa6..556e6f2 100644 --- a/spec/quat_spec.lua +++ b/spec/quat_spec.lua @@ -1,4 +1,233 @@ local quat = require "modules.quat" +local vec3 = require "modules.vec3" describe("quat:", function() + it("tests creating new quaternions", function() + local a = quat() + assert.is.equal(a.x, 0) + assert.is.equal(a.y, 0) + assert.is.equal(a.z, 0) + assert.is.equal(a.w, 1) + assert.is_true(a:is_quat()) + assert.is_true(a:is_real()) + + local b = quat(0, 0, 0, 0) + assert.is.equal(b.x, 0) + assert.is.equal(b.y, 0) + assert.is.equal(b.z, 0) + assert.is.equal(b.w, 0) + assert.is_true(b:is_zero()) + assert.is_true(b:is_imaginary()) + + local c = quat { 2, 3, 4, 1 } + assert.is.equal(c.x, 2) + assert.is.equal(c.y, 3) + assert.is.equal(c.z, 4) + assert.is.equal(c.w, 1) + + local d = quat { x=2, y=3, z=4, w=1 } + assert.is.equal(d.x, 2) + assert.is.equal(d.y, 3) + assert.is.equal(d.z, 4) + assert.is.equal(d.w, 1) + + local e = quat.from_angle_axis(math.pi, vec3.unit_z) + local angle, axis = e:to_angle_axis() + assert.is.equal(angle, math.pi) + assert.is.equal(axis, vec3.unit_z) + + local f = quat.from_direction(vec3():normalize(vec3(5, 10, 15)), vec3.unit_z) + --assert.is.equal(f.x, 0) + --assert.is.equal(f.y, 0) + --assert.is.equal(f.z, 0) + --assert.is.equal(f.w, 0) + + local g = a:clone() + assert.is.equal(g.x, a.x) + assert.is.equal(g.y, a.y) + assert.is.equal(g.z, a.z) + assert.is.equal(g.w, a.w) + end) + + it("tests standard operators", function() + local a = quat(2, 3, 4, 1) + local b = quat(3, 6, 9, 1) + + do + local c = quat():add(a, b) + assert.is.equal(c.x, 5) + assert.is.equal(c.y, 9) + assert.is.equal(c.z, 13) + assert.is.equal(c.w, 2) + + local d = a + b + assert.is.equal(c.x, d.x) + assert.is.equal(c.y, d.y) + assert.is.equal(c.z, d.z) + assert.is.equal(c.w, d.w) + end + + do + local c = quat():sub(a, b) + assert.is.equal(c.x, -1) + assert.is.equal(c.y, -3) + assert.is.equal(c.z, -5) + assert.is.equal(c.w, 0) + + local d = a - b + assert.is.equal(c.x, d.x) + assert.is.equal(c.y, d.y) + assert.is.equal(c.z, d.z) + assert.is.equal(c.w, d.w) + end + + do + local c = quat():mul(a, b) + assert.is.equal(c.x, 8) + assert.is.equal(c.y, 3) + assert.is.equal(c.z, 16) + assert.is.equal(c.w, -59) + + local d = a * b + assert.is.equal(c.x, d.x) + assert.is.equal(c.y, d.y) + assert.is.equal(c.z, d.z) + assert.is.equal(c.w, d.w) + end + + do + local c = quat():scale(a, 3) + assert.is.equal(c.x, 6) + assert.is.equal(c.y, 9) + assert.is.equal(c.z, 12) + assert.is.equal(c.w, 3) + + local d = a * 3 + assert.is.equal(c.x, d.x) + assert.is.equal(c.y, d.y) + assert.is.equal(c.z, d.z) + assert.is.equal(c.w, d.w) + + local e = -a + assert.is.equal(e.x, -a.x) + assert.is.equal(e.y, -a.y) + assert.is.equal(e.z, -a.z) + assert.is.equal(e.w, -a.w) + end + + do + local v = vec3(3, 4, 5) + local c = quat.mul_vec3(vec3(), a, v) + --assert.is.equal(c.x, 0) + --assert.is.equal(c.y, 0) + --assert.is.equal(c.z, 0) + --assert.is.equal(c.w, 0) + + local d = a * v + --assert.is.equal(c.x, d.x) + --assert.is.equal(c.y, d.y) + --assert.is.equal(c.z, d.z) + --assert.is.equal(c.w, d.w) + end + + do + local c = quat():pow(a, 2) + --assert.is.equal(c.x, 0) + --assert.is.equal(c.y, 0) + --assert.is.equal(c.z, 0) + --assert.is.equal(c.w, 0) + + local d = a^2 + --assert.is.equal(c.x, d.x) + --assert.is.equal(c.y, d.y) + --assert.is.equal(c.z, d.z) + --assert.is.equal(c.w, d.w) + end + end) + + it("tests normal, dot", function() + local a = quat(1, 1, 1, 1) + local b = quat(4, 4, 4, 4) + local c = quat():normalize(a) + local d = a:dot(b) + + assert.is.equal(c.x, 0.5) + assert.is.equal(c.y, 0.5) + assert.is.equal(c.z, 0.5) + assert.is.equal(c.w, 0.5) + assert.is.equal(d, 16) + end) + + it("tests length", function() + local a = quat(2, 3, 4, 5) + local b = a:len() + local c = a:len2() + + assert.is.equal(b, math.sqrt(54)) + assert.is.equal(c, 54) + end) + + it("tests interpolation", function() + local a = quat(3, 3, 3, 3) + local b = quat(6, 6, 6, 6) + local s = 0.1 + + local c = quat():lerp(a, b, s) + assert.is.equal(c.x, 0.5) + assert.is.equal(c.y, 0.5) + assert.is.equal(c.z, 0.5) + assert.is.equal(c.w, 0.5) + + local d = quat():slerp(a, b, s) + assert.is.equal(d.x, 0.5) + assert.is.equal(d.y, 0.5) + assert.is.equal(d.z, 0.5) + assert.is.equal(d.w, 0.5) + end) + + it("tests extraction", function() + local a = quat(2, 3, 4, 1) + + local x, y, z, w = a:unpack() + assert.is.equal(x, 2) + assert.is.equal(y, 3) + assert.is.equal(z, 4) + assert.is.equal(w, 1) + + local v = a:to_vec3() + assert.is.equal(v.x, 2) + assert.is.equal(v.y, 3) + assert.is.equal(v.z, 4) + end) + + it("tests conjugate", function() + local a = quat(2, 3, 4, 1) + local b = quat():conjugate(a) + + assert.is.equal(b.x, -2) + assert.is.equal(b.y, -3) + assert.is.equal(b.z, -4) + assert.is.equal(b.w, 1) + end) + + it("tests inverse", function() + local a = quat(1, 1, 1, 1) + local b = quat():inverse(a) + + assert.is.equal(b.x, -0.5) + assert.is.equal(b.y, -0.5) + assert.is.equal(b.z, -0.5) + assert.is.equal(b.w, 0.5) + end) + + it("tests reciprocal", function() + local a = quat(1, 1, 1, 1) + local b = quat():reciprocal(a) + local c = quat():reciprocal(b) + + assert.is.equal(c.x, a.x) + assert.is.equal(c.y, a.y) + assert.is.equal(c.z, a.z) + assert.is.equal(c.w, a.w) + end) end) diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index 09beb24..1fc6fe9 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -3,7 +3,7 @@ local DBL_EPSILON = require("modules.constants").DBL_EPSILON local abs, sqrt = math.abs, math.sqrt describe("vec2:", function() - it("Test creating vectors", function() + it("tests creating vectors", function() -- new empty vector local a = vec2() assert.is.equal(a.x, 0) @@ -30,7 +30,7 @@ describe("vec2:", function() assert.is.equal(d, e) end) - it("Test basic operators", function() + it("tests basic operators", function() local a = vec2(3, 5) local b = vec2(7, 4) local s = 2 @@ -83,7 +83,7 @@ describe("vec2:", function() end end) - it("Test normal, trim, length", function() + it("tests normal, trim, length", function() local a = vec2(3, 5) local b = vec2():normalize(a) local c = vec2():trim(a, 0.5) @@ -94,7 +94,7 @@ describe("vec2:", function() assert.is_true(abs(c:len() - 0.5) < DBL_EPSILON) end) - it("Test distance", function() + it("tests distance", function() local a = vec2(3, 5) local b = vec2(7, 4) @@ -111,7 +111,7 @@ describe("vec2:", function() end end) - it("Test cross product", function() + it("tests cross product", function() local a = vec2(3, 5) local b = vec2(7, 4) local c = a:cross(b) @@ -119,7 +119,7 @@ describe("vec2:", function() assert.is.equal(c, -23) end) - it("Test dot product", function() + it("tests dot product", function() local a = vec2(3, 5) local b = vec2(7, 4) local c = a:dot(b) @@ -127,7 +127,7 @@ describe("vec2:", function() assert.is.equal(c, 41) end) - it("Test lerp", function() + it("tests lerp", function() local a = vec2(3, 5) local b = vec2(7, 4) local s = 0.1 @@ -137,7 +137,7 @@ describe("vec2:", function() assert.is.equal(c.y, 4.9) end) - it("Test unpack", function() + it("tests unpack", function() local a = vec2(3, 5) local x, y = a:unpack()