diff --git a/LICENSE.md b/LICENSE.md index 30abcbc..b9af9d9 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -35,3 +35,26 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# The BSD License (BSD-2-Clause) + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/modules/_private_utils.lua b/modules/_private_utils.lua new file mode 100644 index 0000000..de92786 --- /dev/null +++ b/modules/_private_utils.lua @@ -0,0 +1,11 @@ +-- Functions exported by utils.lua but needed by vec2 or vec3 (which utils.lua requires) + +local private = {} +local floor = math.floor + +function private.round(value, precision) + if precision then return utils.round(value / precision) * precision end + return value >= 0 and floor(value+0.5) or ceil(value-0.5) +end + +return private diff --git a/modules/bound2.lua b/modules/bound2.lua index 7a191bc..7252b99 100644 --- a/modules/bound2.lua +++ b/modules/bound2.lua @@ -168,6 +168,14 @@ function bound2.contains(a, v) and a.max.x >= v.x and a.max.y >= v.y end +-- Round all components of all vectors to nearest int (or other precision). +-- @tparam vec3 a bound to round. +-- @tparam precision Digits after the decimal (round number if unspecified) +-- @treturn vec3 Rounded bound +function bound2.round(a, precision) + return bound2.new(a.min:round(precision), a.max.round(precision)) +end + --- Return a formatted string. -- @tparam bound2 a bound to be turned into a string -- @treturn string formatted @@ -183,7 +191,9 @@ function bound2_mt.__call(_, a, b) end if status then - ffi.metatype(new, bound2_mt) + xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded + ffi.metatype(new, bound2_mt) + end, function() end) end return setmetatable({}, bound2_mt) diff --git a/modules/bound3.lua b/modules/bound3.lua index e6f450a..c5c9751 100644 --- a/modules/bound3.lua +++ b/modules/bound3.lua @@ -168,6 +168,14 @@ function bound3.contains(a, v) and a.max.x >= v.x and a.max.y >= v.y and a.max.z >= v.z end +-- Round all components of all vectors to nearest int (or other precision). +-- @tparam vec3 a bound to round. +-- @tparam precision Digits after the decimal (round number if unspecified) +-- @treturn vec3 Rounded bound +function bound3.round(a, precision) + return bound3.new(a.min:round(precision), a.max:round(precision)) +end + --- Return a formatted string. -- @tparam bound3 a bound to be turned into a string -- @treturn string formatted @@ -183,7 +191,9 @@ function bound3_mt.__call(_, a, b) end if status then - ffi.metatype(new, bound3_mt) + xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded + ffi.metatype(new, bound3_mt) + end, function() end) end return setmetatable({}, bound3_mt) diff --git a/modules/mat4.lua b/modules/mat4.lua index 7db4c5a..6b294d8 100644 --- a/modules/mat4.lua +++ b/modules/mat4.lua @@ -542,10 +542,10 @@ function mat4.look_at(out, a, eye, look_at, up) out[10] = y_axis.z out[11] = z_axis.z out[12] = 0 - out[13] = 0 - out[14] = 0 - out[15] = 0 - out[16] = 1 + out[13] = -out[ 1]*eye.x - out[4+1]*eye.y - out[8+1]*eye.z + out[14] = -out[ 2]*eye.x - out[4+2]*eye.y - out[8+2]*eye.z + out[15] = -out[ 3]*eye.x - out[4+3]*eye.y - out[8+3]*eye.z + out[16] = -out[ 4]*eye.x - out[4+4]*eye.y - out[8+4]*eye.z + 1 return out end @@ -665,7 +665,7 @@ function mat4.to_string(a) return str end ---- Convert a matrix to vec4s. +--- Convert a matrix to row vec4s. -- @tparam mat4 a Matrix to be converted -- @treturn table vec4s function mat4.to_vec4s(a) @@ -677,6 +677,18 @@ function mat4.to_vec4s(a) } end +--- Convert a matrix to col vec4s. +-- @tparam mat4 a Matrix to be converted +-- @treturn table vec4s +function mat4.to_vec4s_cols(a) + return { + { a[1], a[5], a[9], a[13] }, + { a[2], a[6], a[10], a[14] }, + { a[3], a[7], a[11], a[15] }, + { a[4], a[8], a[12], a[16] } + } +end + -- http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ --- Convert a matrix to a quaternion. -- @tparam mat4 a Matrix to be converted @@ -853,7 +865,9 @@ function mat4_mt.__mul(a, b) end if status then - ffi.metatype(new, mat4_mt) + xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded + ffi.metatype(new, mat4_mt) + end, function() end) end return setmetatable({}, mat4_mt) diff --git a/modules/quat.lua b/modules/quat.lua index 9ffd2a9..3805d60 100644 --- a/modules/quat.lua +++ b/modules/quat.lua @@ -465,7 +465,9 @@ function quat_mt.__pow(a, n) end if status then - ffi.metatype(new, quat_mt) + xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded + ffi.metatype(new, quat_mt) + end, function() end) end return setmetatable({}, quat_mt) diff --git a/modules/utils.lua b/modules/utils.lua index 933cbca..e6d0b8f 100644 --- a/modules/utils.lua +++ b/modules/utils.lua @@ -4,6 +4,7 @@ local modules = (...): gsub('%.[^%.]+$', '') .. "." local vec2 = require(modules .. "vec2") local vec3 = require(modules .. "vec3") +local private = require(modules .. "_private_utils") local abs = math.abs local ceil = math.ceil local floor = math.floor @@ -103,10 +104,7 @@ end -- @param value -- @param precision -- @return number -function utils.round(value, precision) - if precision then return utils.round(value / precision) * precision end - return value >= 0 and floor(value+0.5) or ceil(value-0.5) -end +utils.round = private.round --- Wrap `value` around if it exceeds `limit`. -- @param value diff --git a/modules/vec2.lua b/modules/vec2.lua index 01a0b97..bbc509d 100644 --- a/modules/vec2.lua +++ b/modules/vec2.lua @@ -3,6 +3,7 @@ local modules = (...):gsub('%.[^%.]+$', '') .. "." local vec3 = require(modules .. "vec3") +local private = require(modules .. "_private_utils") local acos = math.acos local atan2 = math.atan2 local sqrt = math.sqrt @@ -321,6 +322,14 @@ function vec2.to_polar(a) return radius, theta end +-- Round all components to nearest int (or other precision). +-- @tparam vec2 a Vector to round. +-- @tparam precision Digits after the decimal (round numebr if unspecified) +-- @treturn vec2 Rounded vector +function vec2.round(a, precision) + return vec2.new(private.round(a.x, precision), private.round(a.y, precision)) +end + --- Return a formatted string. -- @tparam vec2 a Vector to be turned into a string -- @treturn string formatted @@ -381,7 +390,9 @@ function vec2_mt.__div(a, b) end if status then - ffi.metatype(new, vec2_mt) + xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded + ffi.metatype(new, vec2_mt) + end, function() end) end return setmetatable({}, vec2_mt) diff --git a/modules/vec3.lua b/modules/vec3.lua index bf3d6bd..07940c5 100644 --- a/modules/vec3.lua +++ b/modules/vec3.lua @@ -1,6 +1,8 @@ --- A 3 component vector. -- @module vec3 +local modules = (...):gsub('%.[^%.]+$', '') .. "." +local private = require(modules .. "_private_utils") local sqrt = math.sqrt local cos = math.cos local sin = math.sin @@ -254,6 +256,14 @@ function vec3.lerp(a, b, s) return a + (b - a) * s end +-- Round all components to nearest int (or other precision). +-- @tparam vec3 a Vector to round. +-- @tparam precision Digits after the decimal (round numebr if unspecified) +-- @treturn vec3 Rounded vector +function vec3.round(a, precision) + return vec3.new(private.round(a.x, precision), private.round(a.y, precision), private.round(a.z, precision)) +end + --- Unpack a vector into individual components. -- @tparam vec3 a Vector to unpack -- @treturn number x @@ -361,7 +371,9 @@ function vec3_mt.__div(a, b) end if status then - ffi.metatype(new, vec3_mt) + xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded + ffi.metatype(new, vec3_mt) + end, function() end) end return setmetatable({}, vec3_mt) diff --git a/spec/bound2_spec.lua b/spec/bound2_spec.lua index 5d2987e..b5324ac 100644 --- a/spec/bound2_spec.lua +++ b/spec/bound2_spec.lua @@ -38,7 +38,7 @@ describe("bound2:", function() it("clones a bound2", function() local a = bound2(vec2(1,2), vec2(4,5)) local b = a:clone() - a.max = new vec2(9,9) + a.max = vec2.new(9,9) assert.is.equal(a.min, b.min) assert.is.not_equal(a.max, b.max) end) @@ -162,13 +162,22 @@ describe("bound2:", function() it("tests for points inside bound2", function() local a = bound2(vec2(1,2), vec2(4,5)) + assert.is_true(a:contains(vec2(1,2))) + assert.is_true(a:contains(vec2(4,5))) assert.is_true(a:contains(vec2(2,3))) assert.is_not_true(a:contains(vec2(0,3))) assert.is_not_true(a:contains(vec2(5,3))) assert.is_not_true(a:contains(vec2(2,1))) assert.is_not_true(a:contains(vec2(2,6))) - assert.is_not_true(a:contains(vec2(2,3))) - assert.is_not_true(a:contains(vec2(2,3))) + end) + + it("rounds a bound2", function() + local a = bound2(vec2(1.1,1.9), vec2(3.9,5.1)):round() + + assert.is.equal(1, a.min.x) + assert.is.equal(2, a.min.y) + assert.is.equal(4, a.max.x) + assert.is.equal(5, a.max.y) end) it("extends a bound2 with a point", function() diff --git a/spec/bound3_spec.lua b/spec/bound3_spec.lua index 5e54d01..fc7a6ed 100644 --- a/spec/bound3_spec.lua +++ b/spec/bound3_spec.lua @@ -46,7 +46,7 @@ describe("bound3:", function() it("clones a bound3", function() local a = bound3(vec3(1,2,3), vec3(4,5,6)) local b = a:clone() - a.max = new vec3(9,9,9) + a.max = vec3.new(9,9,9) assert.is.equal(a.min, b.min) assert.is.not_equal(a.max, b.max) end) @@ -199,6 +199,8 @@ describe("bound3:", function() it("tests for points inside bound3", function() local a = bound3(vec3(1,2,3), vec3(4,5,6)) + assert.is_true(a:contains(vec3(1,2,3))) + assert.is_true(a:contains(vec3(4,5,6))) assert.is_true(a:contains(vec3(2,3,4))) assert.is_not_true(a:contains(vec3(0,3,4))) assert.is_not_true(a:contains(vec3(5,3,4))) @@ -208,6 +210,17 @@ describe("bound3:", function() assert.is_not_true(a:contains(vec3(2,3,7))) end) + it("rounds a bound3", function() + local a = bound3(vec3(1.1,1.9,3), vec3(3.9,5.1,6)):round() + + assert.is.equal(1, a.min.x) + assert.is.equal(2, a.min.y) + assert.is.equal(3, a.min.z) + assert.is.equal(4, a.max.x) + assert.is.equal(5, a.max.y) + assert.is.equal(6, a.max.z) + end) + it("extends a bound3 with a point", function() local min = vec3(1,2,6) local max = vec3(4,5,9) diff --git a/spec/mat4_spec.lua b/spec/mat4_spec.lua index 19b0fff..7e76122 100644 --- a/spec/mat4_spec.lua +++ b/spec/mat4_spec.lua @@ -322,7 +322,12 @@ describe("mat4:", function() end) it("converts a matrix to vec4s", function() - local a = mat4() + local a = mat4 { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + } local v = a:to_vec4s() assert.is_true(type(v) == "table") assert.is_true(type(v[1]) == "table") @@ -330,25 +335,60 @@ describe("mat4:", function() assert.is_true(type(v[3]) == "table") assert.is_true(type(v[4]) == "table") - assert.is.equal(1, v[1][1]) - assert.is.equal(0, v[1][2]) - assert.is.equal(0, v[1][3]) - assert.is.equal(0, v[1][4]) + assert.is.equal(1, v[1][1]) + assert.is.equal(2, v[1][2]) + assert.is.equal(3, v[1][3]) + assert.is.equal(4, v[1][4]) - assert.is.equal(0, v[2][1]) - assert.is.equal(1, v[2][2]) - assert.is.equal(0, v[2][3]) - assert.is.equal(0, v[2][4]) + assert.is.equal(5, v[2][1]) + assert.is.equal(6, v[2][2]) + assert.is.equal(7, v[2][3]) + assert.is.equal(8, v[2][4]) - assert.is.equal(0, v[3][1]) - assert.is.equal(0, v[3][2]) - assert.is.equal(1, v[3][3]) - assert.is.equal(0, v[3][4]) + assert.is.equal(9, v[3][1]) + assert.is.equal(10, v[3][2]) + assert.is.equal(11, v[3][3]) + assert.is.equal(12, v[3][4]) - assert.is.equal(0, v[4][1]) - assert.is.equal(0, v[4][2]) - assert.is.equal(0, v[4][3]) - assert.is.equal(1, v[4][4]) + assert.is.equal(13, v[4][1]) + assert.is.equal(14, v[4][2]) + assert.is.equal(15, v[4][3]) + assert.is.equal(16, v[4][4]) + end) + + it("converts a matrix to vec4s, column-wise", function() + local a = mat4 { + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + } + local v = a:to_vec4s_cols() + assert.is_true(type(v) == "table") + assert.is_true(type(v[1]) == "table") + assert.is_true(type(v[2]) == "table") + assert.is_true(type(v[3]) == "table") + assert.is_true(type(v[4]) == "table") + + assert.is.equal(1, v[1][1]) + assert.is.equal(5, v[1][2]) + assert.is.equal(9, v[1][3]) + assert.is.equal(13, v[1][4]) + + assert.is.equal(2, v[2][1]) + assert.is.equal(6, v[2][2]) + assert.is.equal(10, v[2][3]) + assert.is.equal(14, v[2][4]) + + assert.is.equal(3, v[3][1]) + assert.is.equal(7, v[3][2]) + assert.is.equal(11, v[3][3]) + assert.is.equal(15, v[3][4]) + + assert.is.equal(4, v[4][1]) + assert.is.equal(8, v[4][2]) + assert.is.equal(12, v[4][3]) + assert.is.equal(16, v[4][4]) end) it("converts a matrix to a quaternion", function() diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index 8b826ad..6a2aba0 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -172,8 +172,8 @@ describe("vec2:", function() local a = vec2(3, 5) local r, t = a:to_polar() local b = vec2.from_cartesian(r, t) - assert.is.equal(a.x, b.x) - assert.is.equal(a.y, b.y) + assert.is_true(abs(a.x - b.x) <= DBL_EPSILON*2) -- Allow 2X epsilon error because there were 2 operations. + assert.is_true(abs(a.y - b.y) <= DBL_EPSILON*2) end) it("gets a perpendicular vector", function() @@ -189,6 +189,12 @@ describe("vec2:", function() assert.is.equal("(+0.000,+0.000)", b) end) + it("rounds a 2-vector", function() + local a = vec2(1.1,1.9):round() + assert.is.equal(a.x, 1) + assert.is.equal(a.y, 2) + end) + -- Do this last, to insulate tests from accidental state contamination it("converts a vec3 to vec2 using the constructor", function() local vec3 = require "modules.vec3" diff --git a/spec/vec3_spec.lua b/spec/vec3_spec.lua index 79eca55..fc87135 100644 --- a/spec/vec3_spec.lua +++ b/spec/vec3_spec.lua @@ -202,4 +202,11 @@ describe("vec3:", function() local b = a:to_string() assert.is.equal("(+0.000,+0.000,+0.000)", b) end) + + it("rounds a 3-vector", function() + local a = vec3(1.1,1.9,3):round() + assert.is.equal(a.x, 1) + assert.is.equal(a.y, 2) + assert.is.equal(a.z, 3) + end) end)