From 4f9bc17bf49de0c203618823b5fef91d60e82ba9 Mon Sep 17 00:00:00 2001 From: karai17 Date: Tue, 19 Jul 2016 23:55:21 -0300 Subject: [PATCH] Refactored mat4 Did some tidying up on quat, vec2, vec3, and utils --- modules/mat4.lua | 197 +++++++++++++++++++++++++------------ modules/mesh.lua | 20 ++-- modules/quat.lua | 238 ++++++++++++++++++++++++++------------------- modules/utils.lua | 105 ++++++++++++++++++-- modules/vec2.lua | 97 +++++++++--------- modules/vec3.lua | 120 +++++++++++------------ spec/vec2_spec.lua | 2 +- spec/vec3_spec.lua | 2 +- 8 files changed, 494 insertions(+), 287 deletions(-) diff --git a/modules/mat4.lua b/modules/mat4.lua index 5a94f4f..d414ad8 100644 --- a/modules/mat4.lua +++ b/modules/mat4.lua @@ -77,7 +77,7 @@ function mat4.identity() } end -function mat4.from_axis_angle(angle, axis) +function mat4.from_angle_axis(angle, axis) if type(angle) == "table" then angle, axis = vec3.to_angle_axis(angle) end @@ -119,6 +119,39 @@ function mat4.from_direction(direction, up) return out end +function mat4.from_transform(trans, rot, scale) + local angle, axis = vec3.to_angle_axis(rot) + local l = vec3.len(axis) + + if l == 0 then + return new() + end + + local x, y, z = axis.x / l, axis.y / l, axis.z / l + local c = cos(angle) + local s = sin(angle) + + return new { + x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0, + x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0, + x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0, + trans.x, trans.y, trans.z, 1 + } +end + +function mat4.from_ortho(left, right, top, bottom, near, far) + local out = new() + out[1] = 2 / (right - left) + out[6] = 2 / (top - bottom) + out[11] = -2 / (far - near) + out[13] = -((right + left) / (right - left)) + out[14] = -((top + bottom) / (top - bottom)) + out[15] = -((far + near) / (far - near)) + out[16] = 1 + + return out +end + function mat4.from_perspective(fovy, aspect, near, far) assert(aspect ~= 0) assert(near ~= far) @@ -135,19 +168,6 @@ function mat4.from_perspective(fovy, aspect, near, far) return out end -function mat4.from_ortho(left, right, top, bottom, near, far) - local out = new() - out[1] = 2 / (right - left) - out[6] = 2 / (top - bottom) - out[11] = -2 / (far - near) - out[13] = -((right + left) / (right - left)) - out[14] = -((top + bottom) / (top - bottom)) - out[15] = -((far + near) / (far - near)) - out[16] = 1 - - return out -end - -- Adapted from the Oculus SDK. function mat4.from_hmd_perspective(tanHalfFov, zNear, zFar, flipZ, farAtInfinity) -- CPML is right-handed and intended for GL, so these don't need to be arguments. @@ -236,14 +256,14 @@ function mat4.clone(a) end function mat4.mul(out, a, b) - out[1] = a[1] * b[1] + a[2] * b[5] + a [3] * b[9] + a[4] * b[13] - out[2] = a[1] * b[2] + a[2] * b[6] + a [3] * b[10] + a[4] * b[14] - out[3] = a[1] * b[3] + a[2] * b[7] + a [3] * b[11] + a[4] * b[15] - out[4] = a[1] * b[4] + a[2] * b[8] + a [3] * b[12] + a[4] * b[16] - out[5] = a[5] * b[1] + a[6] * b[5] + a [7] * b[9] + a[8] * b[13] - out[6] = a[5] * b[2] + a[6] * b[6] + a [7] * b[10] + a[8] * b[14] - out[7] = a[5] * b[3] + a[6] * b[7] + a [7] * b[11] + a[8] * b[15] - out[8] = a[5] * b[4] + a[6] * b[8] + a [7] * b[12] + a[8] * b[16] + out[1] = a[1] * b[1] + a[2] * b[5] + a[3] * b[9] + a[4] * b[13] + out[2] = a[1] * b[2] + a[2] * b[6] + a[3] * b[10] + a[4] * b[14] + out[3] = a[1] * b[3] + a[2] * b[7] + a[3] * b[11] + a[4] * b[15] + out[4] = a[1] * b[4] + a[2] * b[8] + a[3] * b[12] + a[4] * b[16] + out[5] = a[5] * b[1] + a[6] * b[5] + a[7] * b[9] + a[8] * b[13] + out[6] = a[5] * b[2] + a[6] * b[6] + a[7] * b[10] + a[8] * b[14] + out[7] = a[5] * b[3] + a[6] * b[7] + a[7] * b[11] + a[8] * b[15] + out[8] = a[5] * b[4] + a[6] * b[8] + a[7] * b[12] + a[8] * b[16] out[9] = a[9] * b[1] + a[10] * b[5] + a[11] * b[9] + a[12] * b[13] out[10] = a[9] * b[2] + a[10] * b[6] + a[11] * b[10] + a[12] * b[14] out[11] = a[9] * b[3] + a[10] * b[7] + a[11] * b[11] + a[12] * b[15] @@ -256,6 +276,15 @@ function mat4.mul(out, a, b) return out end +function mat4.mul_mat41(out, a, b) + out[1] = b[1] * a[1] + b[2] * a[5] + b [3] * a[9] + b[4] * a[13] + out[2] = b[1] * a[2] + b[2] * a[6] + b [3] * a[10] + b[4] * a[14] + out[3] = b[1] * a[3] + b[2] * a[7] + b [3] * a[11] + b[4] * a[15] + out[4] = b[1] * a[4] + b[2] * a[8] + b [3] * a[12] + b[4] * a[16] + + return out +end + function mat4.invert(out, a) out[1] = a[6] * a[11] * a[16] - a[6] * a[12] * a[15] - a[10] * a[7] * a[16] + @@ -350,26 +379,6 @@ function mat4.invert(out, a) return out end -function mat4.compose_world_matrix(t, rot, scale) - local angle, axis = vec3.to_angle_axis(rot) - local l = vec3.len(axis) - - if l == 0 then - return new() - end - - local x, y, z = axis.x / l, axis.y / l, axis.z / l - local c = cos(angle) - local s = sin(angle) - - return new { - x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0, - x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0, - x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0, - t.x, t.y, t.z, 1 - } -end - function mat4.scale(out, a, s) local m = new { s.x, 0, 0, 0, @@ -395,7 +404,7 @@ function mat4.rotate(out, a, angle, axis) local x, y, z = axis.x / l, axis.y / l, axis.z / l local c = cos(angle) local s = sin(angle) - local m = { + local m = new { x*x*(1-c)+c, y*x*(1-c)+z*s, x*z*(1-c)-y*s, 0, x*y*(1-c)-z*s, y*y*(1-c)+c, y*z*(1-c)+x*s, 0, x*z*(1-c)+y*s, y*z*(1-c)-x*s, z*z*(1-c)+c, 0, @@ -423,7 +432,7 @@ function mat4.shear(out, a, yx, zx, xy, zy, xz, yz) local zy = zy or 0 local xz = xz or 0 local yz = yz or 0 - local m = { + local m = new { 1, yx, zx, 0, xy, 1, zy, 0, xz, yz, 1, 0, @@ -476,10 +485,69 @@ function mat4.transpose(out, a) return out end -function mat4.tostring() +-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L317 +-- Note: GLM calls the view matrix "model" +function mat4.project(obj, view, projection, viewport) + local position = { obj.x, obj.y, obj.z, 1 } + + position = mat4.mul(position, mat4.transpose(mat4(), view)) + position = mat4.mul(position, mat4.transpose(mat4(), projection)) + + position[1] = position[1] / position[4] * 0.5 + 0.5 + position[2] = position[2] / position[4] * 0.5 + 0.5 + position[3] = position[3] / position[4] * 0.5 + 0.5 + position[4] = position[4] / position[4] * 0.5 + 0.5 + + position[1] = position[1] * viewport[3] + viewport[1] + position[2] = position[2] * viewport[4] + viewport[2] + + return vec3(position[1], position[2], position[3]) +end + +-- https://github.com/g-truc/glm/blob/master/glm/gtc/matrix_transform.inl#L338 +-- Note: GLM calls the view matrix "model" +function mat4.unproject(win, view, projection, viewport) + local inverse = mat4() + mat4.mul(inverse, view, projection) + mat4.invert(inverse, inverse) + + local position = { win.x, win.y, win.z, 1 } + + position[1] = (position[1] - viewport[1]) / viewport[3] + position[2] = (position[2] - viewport[2]) / viewport[4] + + position[1] = position[1] * 2 - 1 + position[2] = position[2] * 2 - 1 + position[3] = position[3] * 2 - 1 + position[4] = position[4] * 2 - 1 + + mat4.mul(position, inverse, position) + + position[1] = position[1] / position[4] + position[2] = position[2] / position[4] + position[3] = position[3] / position[4] + + return vec3(position[1], position[2], position[3]) +end + +function mat4.is_mat4(a) + if not type(a) == "table" and not type(a) == "cdata" then + return false + end + + for i = 1, 16 do + if type(a[i]) ~= "number" then + return false + end + end + + return true +end + +function mat4.to_string() local str = "[ " for i, v in ipairs(a) do - str = str .. string.format("%2.5f", v) + str = str .. string.format("%+0.3f", v) if i < #a then str = str .. ", " end @@ -488,39 +556,44 @@ function mat4.tostring() return str end +function mat4.to_vec4s(a) + return { + { a[1], a[2], a[3], a[4] }, + { a[5], a[6], a[7], a[8] }, + { a[9], a[10], a[11], a[12] }, + { a[13], a[14], a[15], a[16] } + } +end + function mat4.to_quat(a) local m = mat4.to_vec4s(mat4.transpose(out, a)) - local w = sqrt(1 + m[1][1] + m[2][2] + m[3][3]) / 2 local scale = w * 4 - return quat.normalize(quat(), quat.new( + local q = quat.new( m[3][2] - m[2][3] / scale, m[1][3] - m[3][1] / scale, m[2][1] - m[1][2] / scale, w - )) + ) + return quat.normalize(q, q) end local mat4_mt = {} mat4_mt.__index = mat4 -mat4_mt.__tostring = mat4.tostring +mat4_mt.__tostring = mat4.to_string -function mat4_mt.__call(a) +function mat4_mt.__call(self, a) return mat4.new(a) end -function mat4_mt.__tostring(a) - return mat4.tostring(a) -end - function mat4_mt.__unm(a) return mat4.invert(new(), a) end function mat4_mt.__eq(a, b) - assert(mat4.ismat4(a), "__eq: Wrong argument type for left hand operant. ( expected)") - assert(mat4.ismat4(b), "__eq: Wrong argument type for right hand operant. ( expected)") + assert(mat4.is_mat4(a), "__eq: Wrong argument type for left hand operant. ( expected)") + assert(mat4.is_mat4(b), "__eq: Wrong argument type for right hand operant. ( expected)") for i = 1, 16 do if a[i] ~= b[i] then @@ -532,10 +605,14 @@ function mat4_mt.__eq(a, b) end function mat4_mt.__mul(a, b) - assert(mat4.ismat4(a), "__mul: Wrong argument type for left hand operant. ( expected)") - assert(mat4.ismat4(b), "__mul: Wrong argument type for right hand operant. ( expected)") + assert(mat4.is_mat4(a), "__mul: Wrong argument type for left hand operant. ( expected)") + assert(mat4.is_mat4(b) or #b == 4, "__mul: Wrong argument type for right hand operant. ( or table #4 expected)") - return mat4.mul(new(), a, b) + if mat4.is_mat4(b) then + return mat4.mul(new(), a, b) + end + + return mat4.mul_mat41({}, a, b) end if status then diff --git a/modules/mesh.lua b/modules/mesh.lua index d0b5e7d..34710a3 100644 --- a/modules/mesh.lua +++ b/modules/mesh.lua @@ -1,21 +1,25 @@ --- Mesh utilities -- @module mesh -local current_folder = (...):gsub('%.[^%.]+$', '') .. "." -local vec3 = require(current_folder .. "vec3") - -local mesh = {} +local modules = (...):gsub('%.[^%.]+$', '') .. "." +local vec3 = require(modules .. "vec3") +local mesh = {} function mesh.compute_normal(a, b, c) - return (c - a):cross(b - a):normalize() + local out = vec3() + local ca = vec3.sub(vec3(), c, a) + local ba = vec3.sub(vec3(), b, a) + vec3.cross(out, ca, ba) + vec3.normalize(out, out) + return out end function mesh.average(vertices) - local avg = vec3(0,0,0) + local out = vec3(0, 0, 0) for _, v in ipairs(vertices) do - avg = avg + v + vec3.add(out, out, v) end - return avg / #vertices + return vec3.div(out, out, #vertices) end return mesh diff --git a/modules/quat.lua b/modules/quat.lua index 13605a4..7748760 100644 --- a/modules/quat.lua +++ b/modules/quat.lua @@ -1,20 +1,22 @@ --- A quaternion and associated utilities. -- @module quat -local current_folder = (...):gsub('%.[^%.]+$', '') .. "." - -local constants = require(current_folder .. "constants") -local vec3 = require(current_folder .. "vec3") - -local ffi = require "ffi" -local DOT_THRESHOLD = constants.DOT_THRESHOLD -local FLT_EPSILON = constants.FLT_EPSILON - -local abs, acos, asin, atan2 = math.abs, math.acos, math.asin, math.atan2 -local cos, sin, min, max, pi = math.cos, math.sin, math.min, math.max, math.pi -local sqrt = math.sqrt - -local quat = {} +local modules = (...):gsub('%.[^%.]+$', '') .. "." +local constants = require(modules .. "constants") +local vec3 = require(modules .. "vec3") +local DOT_THRESHOLD = constants.DOT_THRESHOLD +local DBL_EPSILON = constants.DBL_EPSILON +local abs = math.abs +local acos = math.acos +local asin = math.asin +local atan2 = math.atan2 +local cos = math.cos +local sin = math.sin +local min = math.min +local max = math.max +local sqrt = math.sqrt +local pi = math.pi +local quat = {} -- Private constructor. local function new(x, y, z, w) @@ -23,6 +25,9 @@ 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) + -- 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 @@ -48,7 +53,7 @@ function quat.new(x, y, z, w) assert(type(z) == "number", "new: Wrong argument type for z ( expected)") assert(type(w) == "number", "new: Wrong argument type for w ( expected)") - return new(x, y. z, w) + return new(x, y, z, w) -- {x=x, y=y, z=z, w=w} or {x, y, z, w} elseif type(x) == "table" then @@ -66,16 +71,14 @@ function quat.new(x, y, z, w) end --- Create a quaternion from an axis, angle pair. --- @tparam vec3 axis -- @tparam number angle +-- @tparam vec3 axis -- @treturn quat -function quat.from_axis_angle(axis, angle) +function quat.from_angle_axis(angle, axis) local len = vec3.len(axis) - - local s = sin(angle * 0.5) - local c = cos(angle * 0.5) - - return quat.new(axis.x*s, axis.y*s, axis.z*s, c) + local s = sin(angle * 0.5) + local c = cos(angle * 0.5) + return new(axis.x * s, axis.y * s, axis.z * s, c) end --- Create a quaternion from a normalized, up vector pair. @@ -83,17 +86,16 @@ end -- @tparam vec3 up -- @treturn quat function quat.from_direction(normal, up) + local a = vec3.cross(vec3(), up, normal) local d = vec3.dot(up, normal) - local a = vec3() - vec3.cross(a, up, normal) - return quat.new(a.x, a.y, a.z, d+1) + return new(a.x, a.y, a.z, d + 1) end --- Clone a quaternion. -- @tparam quat a -- @treturn quat clone function quat.clone(a) - new(a.x, a.y, a.z, a.w) + return new(a.x, a.y, a.z, a.w) end --- Component-wise add a quaternion. @@ -148,6 +150,7 @@ function quat.mul_vec3(out, a, b) vec3.add(out, out, uuv) vec3.mul(out, out, 2) vec3.add(out, b, out) + return out end --- Pow a quaternion by an exponent @@ -178,6 +181,38 @@ function quat.pow(out, a, n) return out end +--- Normalize a quaternion. +-- @tparam quat out +-- @tparam quat a +-- @treturn quat out +function quat.normalize(out, a) + local l = 1 / quat.len(a) + quat.scale(out, a, l) + return out +end + +--- Return the inner angle between two quaternions. +-- @tparam quat a +-- @tparam quat b +-- @treturn number angle +function quat.dot(a, b) + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w +end + +--- Return the length of a quaternion. +-- @tparam quat a +-- @treturn number len +function quat.len(a) + return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w) +end + +--- Return the squared length of a quaternion. +-- @tparam quat a +-- @treturn number len +function quat.len2(a) + return a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w +end + --- Component-wise scale a quaternion by a scalar. -- @tparam quat out -- @tparam quat a @@ -199,7 +234,7 @@ function quat.conjugate(out, a) out.x = -a.x out.y = -a.y out.z = -a.z - out.w = a.w + out.w = a.w return out end @@ -218,6 +253,7 @@ end -- @tparam quat a -- @treturn quat out function quat.reciprocal(out, a) + assert(not quat.is_zer(a), "Cannot reciprocate a zero quaternion") local l = quat.len2(a) quat.conjugate(out, a) quat.scale(out, out, 1 / l) @@ -270,20 +306,10 @@ function quat.slerp(out, a, b, s) return out end ---- Normalize a quaternion. --- @tparam quat out --- @tparam quat a --- @treturn quat out -function quat.normalize(out, a) - local l = 1 / quat.len(a) - quat.scale(out, a, l) - return out -end - --- Return the imaginary part of the quaternion as a vec3. -- @tparam vec3 out -- @tparam quat a --- @treturn quat out +-- @treturn vec3 out function quat.imaginary(out, a) out.x = a.x out.y = a.y @@ -298,28 +324,6 @@ function quat.real(a) return a.w end ---- Return the inner angle between two quaternions. --- @tparam quat a --- @tparam quat b --- @treturn number angle -function quat.dot(a, b) - return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w -end - ---- Return the length of a quaternion. --- @tparam quat a --- @treturn number len -function quat.len(a) - return sqrt(a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w) -end - ---- Return the squared length of a quaternion. --- @tparam quat a --- @treturn number len -function quat.len2(a) - return a.x * a.x + a.y * a.y + a.z * a.z + a.w * a.w -end - --- Unpack a quaternion into form x,y,z,w. -- @tparam quat a -- @treturn number x @@ -330,17 +334,46 @@ function quat.unpack(a) return a.x, a.y, a.z, a.w end +function quat.to_angle_axis(a) + if a.w > 1 or a.w < -1 then + a = quat.normalize(a, a) + end + + local angle = 2 * acos(a.w) + local s = sqrt(1 - a.w * a.w) + local x, y, z + + if s < constants.DBL_EPSILON then + x = a.x + y = a.y + z = a.z + else + x = a.x / s -- normalize axis + y = a.y / s + z = a.z / s + end + + return angle, vec3(x, y, z) +end + +function quat.to_vec3(out, a) + out.x = a.x + out.y = a.y + out.z = a.z + return out +end + --- Return a string formatted "{x, y, z, w}" -- @tparam quat a -- @treturn string -function quat.tostring(a) +function quat.to_string(a) return string.format("(%+0.3f,%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z, a.w) end --- Return a boolean showing if a table is or is not a quat -- @param q object to be tested -- @treturn boolean -function quat.isquat(q) +function quat.is_quat(q) return ( type(v) == "table" or @@ -352,67 +385,74 @@ function quat.isquat(q) type(v.w) == "number" end -local quat_mt = {} +function quat.is_zero(a) + return + a.x == 0 and + a.y == 0 and + a.z == 0 and + a.w == 0 +end -quat_mt.__index = quat -quat_mt.__tostring = quat.tostring +function quat.is_real(a) + return + a.x == 0 and + a.y == 0 and + a.z == 0 +end -function quat_mt.__call(self, x, y, z) - return quat.new(x, y, z, w) +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(self, x, y, z, w) + return new(x, y, z, w) end function quat_mt.__unm(a) - local temp = quat.new() - quat.scale(temp, a, -1) - return temp + return quat.scale(new(), a, -1) end function quat_mt.__eq(a,b) - assert(quat.isquat(a), "__eq: Wrong argument type for left hand operant. ( expected)") - assert(quat.isquat(b), "__eq: Wrong argument type for right hand operant. ( expected)") + assert(quat.is_quat(a), "__eq: Wrong argument type for left hand operant. ( expected)") + assert(quat.is_quat(b), "__eq: Wrong argument type for right hand operant. ( expected)") return a.x == b.x and a.y == b.y and a.z == b.z and a.w == b.w end function quat_mt.__add(a, b) - assert(quat.isquat(a), "__add: Wrong argument type for left hand operant. ( expected)") - assert(quat.isquat(b), "__add: Wrong argument type for right hand operant. ( expected)") - - local temp = quat.new() - quat.add(temp, a, b) - return temp + assert(quat.is_quat(a), "__add: Wrong argument type for left hand operant. ( expected)") + assert(quat.is_quat(b), "__add: Wrong argument type for right hand operant. ( expected)") + return quat.add(new(), a, b) end function quat_mt.__sub(a, b) - assert(quat.isquat(a), "__sub: Wrong argument type for left hand operant. ( expected)") - assert(quat.isquat(b), "__sub: Wrong argument type for right hand operant. ( expected)") - - local temp = quat.new() - quat.sub(temp, a, b) - return temp + assert(quat.is_quat(a), "__sub: Wrong argument type for left hand operant. ( expected)") + assert(quat.is_quat(b), "__sub: Wrong argument type for right hand operant. ( expected)") + return quat.sub(new(), a, b) end function quat_mt.__mul(a, b) - assert(quat.isquat(a), "__mul: Wrong argument type for left hand operant. ( expected)") - assert(quat.isquat(b) or vec3.isvec3(b), "__mul: Wrong argument type for right hand operant. ( or expected)") + 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)") - if quat.isquat(b) then - local temp = quat.new() - quat.mul(temp, a, b) - return temp - elseif vec3.isvec3(b) then - local temp = vec3() - quat.mul_vec3(temp, a, b) - return temp + if quat.is_quat(b) then + return quat.mul(new(), a, b) end + + if type(b) == "number" then + return quat.scale(new(), a, b) + end + + return quat.mul_vec3(vec3(), a, b) end function quat_mt.__pow(a, n) - assert(quat.isquat(a), "__pow: Wrong argument type for left hand operant. ( expected)") + 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)") - - local temp = quat.new() - quat.pow(temp, a, n) - return temp + return quat.pow(new(), a, n) end if status then diff --git a/modules/utils.lua b/modules/utils.lua index 031153f..a392dfe 100644 --- a/modules/utils.lua +++ b/modules/utils.lua @@ -1,15 +1,25 @@ --- Various utility functions -- @module utils -local utils = {} +local modules = (...): gsub('%.[^%.]+$', '') .. "." +local vec2 = require(modules .. "vec2") +local vec3 = require(modules .. "vec3") +local atan2 = math.atan2 +local acos = math.acos +local sqrt = math.sqrt +local abs = math.abs +local ceil = math.ceil +local floor = math.floor +local log = math.log +local utils = {} -- reimplementation of math.frexp, due to its removal from Lua 5.3 :( -- courtesy of airstruck -local log2 = math.log(2) +local log2 = log(2) local frexp = math.frexp or function(x) if x == 0 then return 0, 0 end - local e = math.floor(math.log(math.abs(x)) / log2 + 1) + local e = floor(log(abs(x)) / log2 + 1) return x / 2 ^ e, e end @@ -27,7 +37,7 @@ end -- @param size -- @return number function utils.deadzone(value, size) - return math.abs(value) >= size and value or 0 + return abs(value) >= size and value or 0 end --- Check if value is equal or greater than threshold. @@ -36,7 +46,7 @@ end -- @return boolean function utils.threshold(value, threshold) -- I know, it barely saves any typing at all. - return math.abs(value) >= threshold + return abs(value) >= threshold end --- Scales a value from one range to another. @@ -79,7 +89,7 @@ end -- @return number function utils.round(value, precision) if precision then return utils.round(value / precision) * precision end - return value >= 0 and math.floor(value+0.5) or math.ceil(value-0.5) + return value >= 0 and floor(value+0.5) or ceil(value-0.5) end --- Wrap `value` around if it exceeds `limit`. @@ -104,4 +114,87 @@ function utils.is_pot(value) return (frexp(value)) == 0.5 end +-- Originally from vec3 +function utils.project_on(out, a, b) + local isvec3 = vec3.is_vec3(out) + local s = + (a.x * b.x + a.y * b.y + isvec3 and a.z * b.z or 0) / + (b.x * b.x + b.y * b.y + isvec3 and b.z * b.z or 0) + + out.x = b.x * s + out.y = b.y * s + out.z = isvec3 and b.z * s or nil + + return out +end + +-- Originally from vec3 +function utils.project_from(out, a, b) + local isvec3 = vec3.is_vec3(out) + local s = + (b.x * b.x + b.y * b.y + isvec3 and b.z * b.z or 0) / + (a.x * b.x + a.y * b.y + isvec3 and a.z * b.z or 0) + + out.x = b.x * s + out.y = b.y * s + out.z = isvec3 and b.z * s or nil + + return out +end + +-- Originally from vec3 +function utils.mirror_on(out, a, b) + local isvec3 = vec3.is_vec3(out) + local s = + (a.x * b.x + a.y * b.y + isvec3 and a.z * b.z or 0) / + (b.x * b.x + b.y * b.y + isvec3 and b.z * b.z or 0) * 2 + out.x = b.x * s - a.x + out.y = b.y * s - a.y + out.z = isvec3 and b.z * s - a.z or nil + return out +end + +-- Originally from vec3 +function utils.reflect(out, i, n) + vec3.mul(out, n, vec3.dot(n, i) * 2) + vec3.sub(out, i, out) + return out +end + +-- Originally from vec3 +function utils.refract(out, i, n, ior) + local d = vec3.dot(n, i) + local k = 1 - ior * ior * (1 - d * d) + + if k >= 0 then + vec3.mul(out, i, ior) + vec3.mul(tmp, n, ior * d + sqrt(k)) + vec3.sub(out, out, tmp) + end + + return out +end + +-- Originally from vec3 +function utils.angle_to(a, b) + if b then + return atan2(a.y - b.y, a.x - b.x) + end + + return atan2(a.y, a.x) +end + +-- Originally from vec3 +function utils.angle_between(a, b) + if b then + if vec2.is_vec2(a) then + return acos(vec2.dot(a, b) / (vec2.len(a) * vec2.len(b))) + end + + return acos(vec3.dot(a, b) / (vec3.len(a) * vec3.len(b))) + end + + return 0 +end + return utils diff --git a/modules/vec2.lua b/modules/vec2.lua index da91584..7134df0 100644 --- a/modules/vec2.lua +++ b/modules/vec2.lua @@ -5,7 +5,6 @@ local atan2 = math.atan2 local sqrt = math.sqrt local cos = math.cos local sin = math.sin -local pi = math.pi local vec2 = {} -- Private constructor. @@ -15,6 +14,10 @@ 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 @@ -55,6 +58,17 @@ function vec2.new(x, y) end end +--- Convert point from polar to cartesian. +-- @tparam vec2 out vector for result to be stored in +-- @tparam number radius radius of the point +-- @tparam number theta angle of the point (in radians) +-- @treturn vec2 +function vec2.from_cartesian(out, radius, theta) + out.x = radius * cos(theta) + out.y = radius * sin(theta) + return out +end + --- Clone a vector. -- @tparam vec2 a vector to be cloned -- @treturn vec2 @@ -173,6 +187,19 @@ function vec2.dist2(a, b) return dx * dx + dy * dy end +function vec2.rotate(out, a, phi) + local c, s = cos(phi), sin(phi) + out.x = c * a.x - s * a.y + out.y = s * a.x + c * a.y + return out +end + +function vec2.perpendicular(out, a) + out.x = -a.y + out.y = a.x + return out +end + --- Lerp between two vectors. -- @tparam vec2 out vector for result to be stored in -- @tparam vec2 a first vector @@ -194,17 +221,10 @@ 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) +function vec2.is_vec2(v) return ( type(v) == "table" or @@ -214,17 +234,6 @@ function vec2.isvec2(v) 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 --- @tparam number theta angle of the point (in radians) --- @treturn vec2 -function vec2.to_cartesian(out, radius, theta) - out.x = radius * cos(theta) - out.y = radius * sin(theta) - return out -end - --- Convert point from cartesian to polar. -- @tparam vec2 a vector to convert -- @treturn number radius @@ -232,71 +241,59 @@ end function vec2.to_polar(a) local radius = sqrt(a.x^2 + a.y^2) local theta = atan2(a.y, a.x) - theta = theta > 0 and theta or theta + 2 * pi + theta = theta > 0 and theta or theta + 2 * math.pi return radius, theta end -local vec2_mt = {} +--- Return a string formatted "{x, y}" +-- @tparam vec2 a the vector to be turned into a string +-- @treturn string +function vec2.to_string(a) + return string.format("(%+0.3f,%+0.3f)", a.x, a.y) +end -vec2_mt.__index = vec2 -vec2_mt.__tostring = vec2.tostring +local vec2_mt = {} +vec2_mt.__index = vec2 +vec2_mt.__tostring = vec2.to_string function vec2_mt.__call(self, x, y) return vec2.new(x, y) end -function vec2_mt.__tostring(a) - return vec2.tostring(a) -end - function vec2_mt.__unm(a) return vec2.new(-a.x, -a.y) end function vec2_mt.__eq(a,b) - assert(vec2.isvec2(a), "__eq: Wrong argument type for left hand operant. ( expected)") - assert(vec2.isvec2(b), "__eq: Wrong argument type for right hand operant. ( expected)") - + assert(vec2.is_vec2(a), "__eq: Wrong argument type for left hand operant. ( expected)") + assert(vec2.is_vec2(b), "__eq: Wrong argument type for right hand operant. ( expected)") return a.x == b.x and a.y == b.y end function vec2_mt.__add(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)") - + assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operant. ( expected)") + assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operant. ( expected)") return vec2.add(new(), a, b) 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)") - + assert(vec2.is_vec2(a), "__add: Wrong argument type for left hand operant. ( expected)") + assert(vec2.is_vec2(b), "__add: Wrong argument type for right hand operant. ( expected)") return vec2.sub(new(), a, b) end function vec2_mt.__mul(a, b) - local isvecb = vec2.isvec2(b) - a, b = isvecb and b or a, isvecb and a or b - - assert(vec2.isvec2(a), "__mul: Wrong argument type for left hand operant. ( expected)") + assert(vec2.is_vec2(a), "__mul: Wrong argument type for left hand operant. ( expected)") assert(type(b) == "number", "__mul: Wrong argument type for right hand operant. ( expected)") - return vec2.mul(new(), a, b) end function vec2_mt.__div(a, b) - local isvecb = vec2.isvec2(b) - a, b = isvecb and b or a, isvecb and a or b - - assert(vec2.isvec2(a), "__div: Wrong argument type for left hand operant. ( expected)") + assert(vec2.is_vec2(a), "__div: Wrong argument type for left hand operant. ( expected)") assert(type(b) == "number", "__div: Wrong argument type for right hand operant. ( expected)") - return vec2.div(new(), a, b) end -vec2.unit_x = new(1, 0) -vec2.unit_y = new(0, 1) - if status then ffi.metatype(new, vec2_mt) end diff --git a/modules/vec3.lua b/modules/vec3.lua index 5bcac65..62d0e6a 100644 --- a/modules/vec3.lua +++ b/modules/vec3.lua @@ -11,6 +11,11 @@ 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 @@ -185,6 +190,36 @@ function vec3.dist2(a, b) return dx * dx + dy * dy + dz * dz end +--- Rotate vector about an axis. +-- @param phi Amount to rotate, in radians +-- @param axis Axis to rotate by +-- @return vec3 +function vec3.rotate(out, a, phi, axis) + if not vec3.is_vec3(axis) then + return a + end + + local u = vec3.normalize(vec3(), axis) + local c, s = cos(phi), sin(phi) + + -- Calculate generalized rotation matrix + local m1 = new((c + u.x * u.x * (1 - c)), (u.x * u.y * (1 - c) - u.z * s), (u.x * u.z * (1 - c) + u.y * s)) + local m2 = new((u.y * u.x * (1 - c) + u.z * s), (c + u.y * u.y * (1 - c)), (u.y * u.z * (1 - c) - u.x * s)) + local m3 = new((u.z * u.x * (1 - c) - u.y * s), (u.z * u.y * (1 - c) + u.x * s), (c + u.z * u.z * (1 - c)) ) + + out.x = vec3.dot(a, m1) + out.y = vec3.dot(a, m2) + out.z = vec3.dot(a, m3) + return out +end + +function vec3.perpendicular(out, a) + out.x = -a.y + out.y = a.x + out.z = 0 + return out +end + --- Lerp between two vectors. -- @tparam vec3 out vector for result to be stored in -- @tparam vec3 a first vector @@ -207,108 +242,69 @@ function vec3.unpack(a) return a.x, a.y, a.z end ---- Return a string formatted "{x, y, z}" --- @tparam vec3 a the vector to be turned into a string --- @treturn string -function vec3.tostring(a) - return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z) -end - --- Return a boolean showing if a table is or is not a vec3 -- @param v the object to be tested -- @treturn boolean -function vec3.isvec3(v) +function vec3.is_vec3(a) return ( - type(v) == "table" or - type(v) == "cdata" + type(a) == "table" or + type(a) == "cdata" ) and - type(v.x) == "number" and - type(v.y) == "number" and - type(v.z) == "number" + type(a.x) == "number" and + type(a.y) == "number" and + type(a.z) == "number" +end +--- Return a string formatted "{x, y, z}" +-- @tparam vec3 a the vector to be turned into a string +-- @treturn string +function vec3.to_string(a) + return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z) 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 -vec3_mt.__tostring = vec3.tostring +local vec3_mt = {} +vec3_mt.__index = vec3 +vec3_mt.__tostring = vec3.to_string function vec3_mt.__call(self, x, y, z) return vec3.new(x, y, z) end -function vec3_mt.__tostring(a) - return vec3.tostring(a) -end - function vec3_mt.__unm(a) return vec3.new(-a.x, -a.y, -a.z) end function vec3_mt.__eq(a,b) - assert(vec3.isvec3(a), "__eq: Wrong argument type for left hand operant. ( expected)") - assert(vec3.isvec3(b), "__eq: Wrong argument type for right hand operant. ( expected)") - + assert(vec3.is_vec3(a), "__eq: Wrong argument type for left hand operant. ( expected)") + assert(vec3.is_vec3(b), "__eq: Wrong argument type for right hand operant. ( expected)") return a.x == b.x and a.y == b.y and a.z == b.z end function vec3_mt.__add(a, b) - assert(vec3.isvec3(a), "__add: Wrong argument type for left hand operant. ( expected)") - assert(vec3.isvec3(b), "__add: Wrong argument type for right hand operant. ( expected)") - + assert(vec3.is_vec3(a), "__add: Wrong argument type for left hand operant. ( expected)") + assert(vec3.is_vec3(b), "__add: Wrong argument type for right hand operant. ( expected)") return vec3.add(new(), a, b) end function vec3_mt.__sub(a, b) - assert(vec3.isvec3(a), "__sub: Wrong argument type for left hand operant. ( expected)") - assert(vec3.isvec3(b), "__sub: Wrong argument type for right hand operant. ( expected)") - + assert(vec3.is_vec3(a), "__sub: Wrong argument type for left hand operant. ( expected)") + assert(vec3.is_vec3(b), "__sub: Wrong argument type for right hand operant. ( expected)") return vec3.sub(new(), a, b) end function vec3_mt.__mul(a, b) - local isvecb = vec3.isvec3(b) - a, b = isvecb and b or a, isvecb and a or b - - assert(vec3.isvec3(a), "__mul: Wrong argument type for left hand operant. ( expected)") + assert(vec3.is_vec3(a), "__mul: Wrong argument type for left hand operant. ( expected)") assert(type(b) == "number", "__mul: Wrong argument type for right hand operant. ( expected)") - return vec3.mul(new(), a, b) end function vec3_mt.__div(a, b) - local isvecb = vec3.isvec3(b) - a, b = isvecb and b or a, isvecb and a or b - - assert(vec3.isvec3(a), "__div: Wrong argument type for left hand operant. ( expected)") + assert(vec3.is_vec3(a), "__div: Wrong argument type for left hand operant. ( expected)") assert(type(b) == "number", "__div: Wrong argument type for right hand operant. ( expected)") - return vec3.div(new(), a, b) end -vec3.unit_x = new(1, 0, 0) -vec3.unit_y = new(0, 1, 0) -vec3.unit_z = new(0, 0, 1) - if status then ffi.metatype(new, vec3_mt) end diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index 9471e0f..f22abf7 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -8,7 +8,7 @@ describe("vec2:", function() local a = vec2() assert.is.equal(a.x, 0) assert.is.equal(a.y, 0) - assert.is_true(vec2.isvec2(a)) + assert.is_true(vec2.is_vec2(a)) -- new vector from table local b = vec2 { 0, 0 } diff --git a/spec/vec3_spec.lua b/spec/vec3_spec.lua index c5b1da7..c3581ad 100644 --- a/spec/vec3_spec.lua +++ b/spec/vec3_spec.lua @@ -9,7 +9,7 @@ describe("vec3:", function() assert.is.equal(a.x, 0) assert.is.equal(a.y, 0) assert.is.equal(a.z, 0) - assert.is_true(vec3.isvec3(a)) + assert.is_true(vec3.is_vec3(a)) -- new vector from table local b = vec3 { 0, 0, 0 }