diff --git a/modules/utils.lua b/modules/utils.lua index 27262d3..031153f 100644 --- a/modules/utils.lua +++ b/modules/utils.lua @@ -101,7 +101,7 @@ end function utils.is_pot(value) -- found here: https://love2d.org/forums/viewtopic.php?p=182219#p182219 -- check if a number is a power-of-two - return (frexp(value)) == 0.5 + return (frexp(value)) == 0.5 end return utils diff --git a/modules/vec2.lua b/modules/vec2.lua index a701a5f..ebaa16b 100644 --- a/modules/vec2.lua +++ b/modules/vec2.lua @@ -1,13 +1,11 @@ --- A 2 component vector. -- @module vec2 -local atan2, sqrt, pi = math.atan2, math.sqrt, math.pi -local cos, sin = math.cos, math.sin - +local atan2, sqrt,cos, sin, pi = math.atan2, math.sqrt, math.cos, math,sin, math.pi local vec2 = {} -- Private constructor. -local function new(x, y, z) +local function new(x, y) local v = {} v.x, v.y = x, y return setmetatable(v, vec2_mt) @@ -30,7 +28,7 @@ end -- scalar to fill the vector eg. {x, x} -- @tparam number y y component function vec2.new(x, y) - -- number, number, number + -- number, number if x and y then assert(type(x) == "number", "new: Wrong argument type for x ( expected)") assert(type(y) == "number", "new: Wrong argument type for y ( expected)") @@ -45,7 +43,7 @@ function vec2.new(x, y) return new(x, y) - -- {x, x, x} eh. {0, 0, 0}, {3, 3, 3} + -- {x, x} eh. {0, 0}, {3, 3} elseif type(x) == "number" then return new(x, x) else @@ -57,7 +55,7 @@ end -- @tparam vec2 a vector to be cloned -- @treturn vec2 function vec2.clone(a) - return new(a.x, a.y, a.z) + return new(a.x, a.y) end --- Add two vectors. @@ -117,7 +115,7 @@ end function vec2.trim(out, a, len) len = math.min(vec2.len(a), len) vec2.normalize(out, a) - vec2.mul(out, len) + vec2.mul(out, out, len) return out end @@ -184,6 +182,34 @@ function vec2.lerp(out, a, b, s) return out end +--- Unpack a vector into form x,y +-- @tparam vec2 a first vector +-- @treturn number x component +-- @treturn number y component +function vec2.unpack(a) + return a.x, a.y +end + +--- Return a string formatted "{x, y}" +-- @tparam vec2 a the vector to be turned into a string +-- @treturn string +function vec2.tostring(a) + return string.format("(%+0.3f,%+0.3f)", a.x, a.y) +end + +--- Return a boolean showing if a table is or is not a vec2 +-- @param v the object to be tested +-- @treturn boolean +function vec2.isvec2(v) + return + ( + type(v) == "table" or + type(v) == "cdata" + ) and + type(v.x) == "number" and + type(v.y) == "number" +end + --- Convert point from polar to cartesian. -- @tparam vec2 out vector for result to be stored in -- @tparam number radius radius of the point @@ -206,38 +232,13 @@ function vec2.to_polar(a) return radius, theta end ---- Unpack a vector into form x,y --- @tparam vec2 a first vector --- @treturn number x component --- @treturn number y component -function vec2.unpack(a) - return a.x, a.y -end - ---- Return a string formatted "{x, y}" --- @tparam vec2 a the vector to be turned into a string --- @treturn string -function vec2.tostring(a) - return string.format("(%+0.3f,%+0.3f)", a.x, a.y) -end - ---- Return a boolean showing if a table is or is not a vec2 --- @param v the object to be tested --- @treturn boolean -function vec2.isvec2(v) - return - type(v) == "table" and - type(v.x) == "number" and - type(v.y) == "number" -end - local vec2_mt = {} vec2_mt.__index = vec2 vec2_mt.__tostring = vec2.tostring -function vec2_mt.__call(self, x, y, z) - return vec2.new(x, y, z) +function vec2_mt.__call(self, x, y) + return vec2.new(x, y) end function vec2_mt.__unm(a) @@ -260,6 +261,15 @@ function vec2_mt.__add(a, b) return temp end +function vec2_mt.__sub(a, b) + assert(vec2.isvec2(a), "__add: Wrong argument type for left hand operant. ( expected)") + assert(vec2.isvec2(b), "__add: Wrong argument type for right hand operant. ( expected)") + + local temp = vec2.new() + vec2.sub(temp, a, b) + return temp +end + function vec2_mt.__mul(a, b) local isvecb = vec2.isvec2(b) a, b = isvecb and b or a, isvecb and a or b @@ -284,6 +294,9 @@ function vec2_mt.__div(a, b) return temp end +vec2.unit_x = vec2.new(1, 0) +vec2.unit_y = vec2.new(0, 1) + if status then ffi.metatype(new, vec2_mt) end diff --git a/modules/vec3.lua b/modules/vec3.lua index d977abd..ab337a5 100644 --- a/modules/vec3.lua +++ b/modules/vec3.lua @@ -1,7 +1,7 @@ --- A 3 component vector. -- @module vec3 -local sqrt= math.sqrt +local sqrt = math.sqrt local vec3 = {} -- Private constructor. @@ -57,7 +57,6 @@ function vec3.new(x, y, z) end end - --- Clone a vector. -- @tparam vec3 a vector to be cloned -- @treturn vec3 @@ -65,7 +64,6 @@ function vec3.clone(a) return new(a.x, a.y, a.z) end - --- Add two vectors. -- @tparam vec3 out vector to store the result -- @tparam vec3 a Left hand operant @@ -110,17 +108,6 @@ function vec3.div(out, a, b) return out end ---- Get the cross product of two vectors. --- @tparam vec3 out vector to store the result --- @tparam vec3 a Left hand operant --- @tparam vec3 b Right hand operant -function vec3.cross(out, a, b) - out.x = a.y * b.z - a.z * b.y - out.y = a.z * b.x - a.x * b.z - out.z = a.x * b.y - a.y * b.x - return out -end - --- Get the normal of a vector. -- @tparam vec3 out vector to store the result -- @tparam vec3 a vector to normalize @@ -139,38 +126,18 @@ end function vec3.trim(out, a, len) len = math.min(vec3.len(a), len) vec3.normalize(out, a) - vec3.mul(out, len) + vec3.mul(out, out, len) return out end -function vec3.reflect(out, i, n) - vec3.mul(out, n, 2.0 * vec3.dot(n, i)) - vec3.sub(out, i, out) - return out -end - -function vec3.refract(out, i, n, ior) - local d = vec3.dot(n, i) - local k = 1.0 - ior * ior * (1.0 - d * d) - if k >= 0.0 then - vec3.mul(out, i, ior) - vec3.mul(tmp, n, ior * d + sqrt(k)) - vec3.sub(out, out, tmp) - end - - return out -end - ---- Lerp between two vectors. --- @tparam vec3 out vector for result to be stored in --- @tparam vec3 a first vector --- @tparam vec3 b second vector --- @tparam number s step value --- @treturn vec3 -function vec3.lerp(out, a, b, s) - vec3.sub(out, b, a) - vec3.mul(out, out, s) - vec3.add(out, out, a) +--- Get the cross product of two vectors. +-- @tparam vec3 out vector to store the result +-- @tparam vec3 a Left hand operant +-- @tparam vec3 b Right hand operant +function vec3.cross(out, a, b) + out.x = a.y * b.z - a.z * b.y + out.y = a.z * b.x - a.x * b.z + out.z = a.x * b.y - a.y * b.x return out end @@ -218,6 +185,19 @@ function vec3.dist2(a, b) return dx * dx + dy * dy + dz * dz end +--- Lerp between two vectors. +-- @tparam vec3 out vector for result to be stored in +-- @tparam vec3 a first vector +-- @tparam vec3 b second vector +-- @tparam number s step value +-- @treturn vec3 +function vec3.lerp(out, a, b, s) + vec3.sub(out, b, a) + vec3.mul(out, out, s) + vec3.add(out, out, a) + return out +end + --- Unpack a vector into form x,y,z -- @tparam vec3 a first vector -- @treturn number x component @@ -239,12 +219,34 @@ end -- @treturn boolean function vec3.isvec3(v) return - type(v) == "table" and + ( + type(v) == "table" or + type(v) == "cdata" + + ) and type(v.x) == "number" and type(v.y) == "number" and type(v.z) == "number" end +function vec3.reflect(out, i, n) + vec3.mul(out, n, 2.0 * vec3.dot(n, i)) + vec3.sub(out, i, out) + return out +end + +function vec3.refract(out, i, n, ior) + local d = vec3.dot(n, i) + local k = 1.0 - ior * ior * (1.0 - d * d) + if k >= 0.0 then + vec3.mul(out, i, ior) + vec3.mul(tmp, n, ior * d + sqrt(k)) + vec3.sub(out, out, tmp) + end + + return out +end + local vec3_mt = {} vec3_mt.__index = vec3 diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index 1821890..214e846 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -1,4 +1,188 @@ -local vec2 = require "modules.vec2" +local vec2 = require "modules.vec2" +local DBL_EPSILON = require("modules.constants").DBL_EPSILON +local abs, sqrt = math.abs, math.sqrt describe("vec2:", function() + it("Test creating vectors", function() + -- new empty vector + local a = vec2() + assert.is.equal(a.x, 0) + assert.is.equal(a.y, 0) + assert.is_true(vec2.isvec2(a)) + + -- new vector from table + local b = vec2 { 0, 0 } + assert.is.equal(b.x, 0) + assert.is.equal(b.y, 0) + + local c = vec2 { x=0, y=0 } + assert.is.equal(c.x, 0) + assert.is.equal(c.y, 0) + + -- new vector from numbers + local d = vec2(3.14159, -2.808) + assert.is.equal(d.x, 3.14159) + assert.is.equal(d.y, -2.808) + + -- new vector from other vector + local e = vec2.clone(d) + assert.is.equal(d, e) + + local f = d:clone() + assert.is.equal(d, f) + end) + + it("Test basic operators", function() + local a = vec2(3, 5) + local b = vec2(7, 4) + local s = 2 + + -- Add + do + local c = vec2.add(vec2(), a, b) + assert.is.equal(c.x, 10) + assert.is.equal(c.y, 9) + + local d = a + b + assert.is.equal(c, d) + end + + -- Subtract + do + local c = vec2.sub(vec2(), a, b) + assert.is.equal(c.x, -4) + assert.is.equal(c.y, 1) + + local d = a - b + assert.is.equal(c, d) + end + + -- Multiply + do + local c = vec2.mul(vec2(), a, s) + assert.is.equal(c.x, 6) + assert.is.equal(c.y, 10) + + local d = a * s + assert.is.equal(c, d) + end + + -- Divide + do + local c = vec2.div(vec2(), a, s) + assert.is.equal(c.x, 1.5) + assert.is.equal(c.y, 2.5) + + local d = a / s + assert.is.equal(c, d) + end + + -- Sign flip + do + local c = -a + assert.is.equal(c.x, -3) + assert.is.equal(c.y, -5) + end + end) + + it("Test normal, trim, length", function() + local a = vec2(3, 5) + + local b = vec2.normalize(vec2(), a) + assert.is_true(abs(b:len() - 1) < DBL_EPSILON) + assert.is_true(abs(b:len() - 1) < DBL_EPSILON) + assert.is_true(abs(b:len2() - 1) < DBL_EPSILON * 2) + + local c = vec2.trim(vec2(), a, 0.5) + assert.is_true(abs(c:len() - 0.5) < DBL_EPSILON) + end) + + it("Test distance", function() + local a = vec2(3, 5) + local b = vec2(7, 4) + + -- Distance + do + local c = vec2.dist(a, b) + assert.is.equal(c, sqrt(17)) + + local d = a:dist(b) + assert.is.equal(c, d) + end + + -- Distance Squared + do + local c = vec2.dist2(a, b) + assert.is.equal(c, 17) + + local d = a:dist2(b) + assert.is.equal(c, d) + end + end) + + it("Test cross product", function() + do + local a = vec2(3, 5) + local b = vec2(7, 4) + + local c = vec2.cross(a, b) + assert.is.equal(c, -23) + + local d = a:cross(b) + assert.is.equal(c, d) + end + end) + + it("Test dot product", function() + do + local a = vec2(3, 5) + local b = vec2(7, 4) + + local c = vec2.dot(a, b) + assert.is.equal(c, 41) + + local d = a:dot(b) + assert.is.equal(c, d) + end + end) + + it("Test lerp", function() + local a = vec2(3, 5) + local b = vec2(7, 4) + local s = 0.1 + + local c = vec2.lerp(vec2(), a, b, s) + assert.is.equal(c.x, 3.4) + assert.is.equal(c.y, 4.9) + end) + + it("Test unpack", function() + local a = vec2(3, 5) + + do + local x, y = vec2.unpack(a) + assert.is.equal(x, 3) + assert.is.equal(y, 5) + end + + do + local x, y = a:unpack() + assert.is.equal(x, 3) + assert.is.equal(y, 5) + end + end) end) + +--[[ + +-- TODO: To Cartesian +do + local a = vec2(3, 5) +end + +-- TODO: To Polar +do + local a = vec2(3, 5) +end + +--]] diff --git a/spec/vec3_spec.lua b/spec/vec3_spec.lua index f378bd9..73779c4 100644 --- a/spec/vec3_spec.lua +++ b/spec/vec3_spec.lua @@ -1,4 +1,199 @@ -local vec3 = require "modules.vec3" +local vec3 = require "modules.vec3" +local DBL_EPSILON = require("modules.constants").DBL_EPSILON +local abs, sqrt = math.abs, math.sqrt describe("vec3:", function() + it("Test creating vectors", function() + -- new empty vector + local a = vec3() + assert.is.equal(a.x, 0) + assert.is.equal(a.y, 0) + assert.is.equal(a.z, 0) + assert.is_true(vec3.isvec3(a)) + + -- new vector from table + local b = vec3 { 0, 0, 0 } + assert.is.equal(b.x, 0) + assert.is.equal(b.y, 0) + assert.is.equal(b.z, 0) + + local c = vec3 { x=0, y=0, z=0 } + assert.is.equal(c.x, 0) + assert.is.equal(c.y, 0) + assert.is.equal(c.z, 0) + + -- new vector from numbers + local d = vec3(3.14159, -2.808, 1.337) + assert.is.equal(d.x, 3.14159) + assert.is.equal(d.y, -2.808) + assert.is.equal(d.z, 1.337) + + -- new vector from other vector + local e = vec3.clone(d) + assert.is.equal(d, e) + + local f = d:clone() + assert.is.equal(d, f) + end) + + it("Test basic operators", function() + local a = vec3(3, 5, 7) + local b = vec3(7, 4, 1) + local s = 2 + + -- Add + do + local c = vec3.add(vec3(), a, b) + assert.is.equal(c.x, 10) + assert.is.equal(c.y, 9) + assert.is.equal(c.z, 8) + + local d = a + b + assert.is.equal(c, d) + end + + -- Subtract + do + local c = vec3.sub(vec3(), a, b) + assert.is.equal(c.x, -4) + assert.is.equal(c.y, 1) + assert.is.equal(c.z, 6) + + local d = a - b + assert.is.equal(c, d) + end + + -- Multiply + do + local c = vec3.mul(vec3(), a, s) + assert.is.equal(c.x, 6) + assert.is.equal(c.y, 10) + assert.is.equal(c.z, 14) + + local d = a * s + assert.is.equal(c, d) + end + + -- Divide + do + local c = vec3.div(vec3(), a, s) + assert.is.equal(c.x, 1.5) + assert.is.equal(c.y, 2.5) + assert.is.equal(c.z, 3.5) + + local d = a / s + assert.is.equal(c, d) + end + + -- Sign flip + do + local c = -a + assert.is.equal(c.x, -3) + assert.is.equal(c.y, -5) + assert.is.equal(c.z, -7) + end + end) + + it("Test normal, trim, length", function() + local a = vec3(3, 5, 7) + + local b = vec3.normalize(vec3(), a) + assert.is_true(abs(b:len() - 1) < DBL_EPSILON) + assert.is_true(abs(b:len() - 1) < DBL_EPSILON) + assert.is_true(abs(b:len2() - 1) < DBL_EPSILON * 2) + + local c = vec3.trim(vec3(), a, 0.5) + assert.is_true(abs(c:len() - 0.5) < DBL_EPSILON) + end) + + it("Test distance", function() + local a = vec3(3, 5, 7) + local b = vec3(7, 4, 1) + + -- Distance + do + local c = vec3.dist(a, b) + assert.is.equal(c, sqrt(53)) + + local d = a:dist(b) + assert.is.equal(c, d) + end + + -- Distance Squared + do + local c = vec3.dist2(a, b) + assert.is.equal(c, 53) + + local d = a:dist2(b) + assert.is.equal(c, d) + end + end) + + it("Test cross product", function() + do + local a = vec3(3, 5, 7) + local b = vec3(7, 4, 1) + + local c = vec3.cross(vec3(), a, b) + assert.is.equal(c.x, -23) + assert.is.equal(c.y, 46) + assert.is.equal(c.z, -23) + end + end) + + it("Test dot product", function() + do + local a = vec3(3, 5, 7) + local b = vec3(7, 4, 1) + + local c = vec3.dot(a, b) + assert.is.equal(c, 48) + + local d = a:dot(b) + assert.is.equal(c, d) + end + end) + + it("Test lerp", function() + local a = vec3(3, 5, 7) + local b = vec3(7, 4, 1) + local s = 0.1 + + local c = vec3.lerp(vec3(), a, b, s) + assert.is.equal(c.x, 3.4) + assert.is.equal(c.y, 4.9) + assert.is.equal(c.z, 6.4) + end) + + it("Test unpack", function() + local a = vec3(3, 5, 7) + + do + local x, y, z = vec3.unpack(a) + assert.is.equal(x, 3) + assert.is.equal(y, 5) + assert.is.equal(z, 7) + end + + do + local x, y, z = a:unpack() + assert.is.equal(x, 3) + assert.is.equal(y, 5) + assert.is.equal(z, 7) + end + end) end) + +--[[ + +-- TODO: Reflect +do + local a = vec3(3, 5, 7) +end + +-- TODO: Refract +do + local a = vec3(3, 5, 7) +end + +--]]