From 7fa17854697470eea382c3e60061dec763446f7c Mon Sep 17 00:00:00 2001 From: mcc Date: Fri, 29 Nov 2019 17:16:20 -0500 Subject: [PATCH 1/9] Fix failing tests; this involves changing 2 bad tests and 1 bad behavior: - vec2 to_polar/from_cartesian tests were testing for equality rather than using an epsilon. - bound2.contains had two tests that were plain wrong. - While I'm fixing the bounds test, bound2.contains and bound3.contains probably ought to test their own min and max values for inclusion. - The implementation of mat4.look_at appears to be wrong. The final column was being set to 0,0,0,1 which comparing against other implementations does not seem to be correct. My replacement code is modeled on the method used in mat4x4_look_at() in linmath.h in GLFW, which says it's a reimplementation on the method from gluLookAt(). With this change the test passes as originally written. --- modules/mat4.lua | 8 ++++---- spec/bound2_spec.lua | 4 ++-- spec/bound3_spec.lua | 2 ++ spec/vec2_spec.lua | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/modules/mat4.lua b/modules/mat4.lua index 7db4c5a..67a09ba 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 diff --git a/spec/bound2_spec.lua b/spec/bound2_spec.lua index a23322e..2f46d99 100644 --- a/spec/bound2_spec.lua +++ b/spec/bound2_spec.lua @@ -162,13 +162,13 @@ 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("checks for bound2.zero", function() diff --git a/spec/bound3_spec.lua b/spec/bound3_spec.lua index e3ae232..fc5c06b 100644 --- a/spec/bound3_spec.lua +++ b/spec/bound3_spec.lua @@ -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))) diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index bdab0bb..6e4dcb5 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -166,8 +166,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() From 7667ea9a3dcb6d75ffa353e15394bbc85f58afc4 Mon Sep 17 00:00:00 2001 From: mcc Date: Fri, 29 Nov 2019 17:55:03 -0500 Subject: [PATCH 2/9] Fix typo in bound2 test which was breaking under LuaJIT --- spec/bound2_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bound2_spec.lua b/spec/bound2_spec.lua index 2f46d99..303cf5f 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) From d845a479acb32b8911d3c302013996c172e5cb34 Mon Sep 17 00:00:00 2001 From: mcc Date: Fri, 29 Nov 2019 21:08:04 -0500 Subject: [PATCH 3/9] Fix typo in bound3 test which was breaking under LuaJIT --- spec/bound3_spec.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bound3_spec.lua b/spec/bound3_spec.lua index fc5c06b..5b3287b 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) From a86935a39b4586782f9a7be2f79ea618d192cab8 Mon Sep 17 00:00:00 2001 From: mcc Date: Fri, 29 Nov 2019 21:13:30 -0500 Subject: [PATCH 4/9] Allow ffi.metatype to fail so that "busted" unit tests work On Github we run unit tests inside "busted". At the start of each test "busted" does some sort of trick (clearing package.loaded)? Which causes "require" to run again for all lua files. This breaks ffi.metatype with error "cannot change a protected metatable" if it is called twice with a single type name, since this is true global state. To work around this this patch wraps ffi.metatype calls in a xpcall() so that failure is silently ignored. --- modules/bound2.lua | 4 +++- modules/bound3.lua | 4 +++- modules/mat4.lua | 4 +++- modules/quat.lua | 4 +++- modules/vec2.lua | 4 +++- modules/vec3.lua | 4 +++- 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/modules/bound2.lua b/modules/bound2.lua index c3639c8..22da310 100644 --- a/modules/bound2.lua +++ b/modules/bound2.lua @@ -160,7 +160,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 4209f84..8c26236 100644 --- a/modules/bound3.lua +++ b/modules/bound3.lua @@ -160,7 +160,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 67a09ba..3cd06f5 100644 --- a/modules/mat4.lua +++ b/modules/mat4.lua @@ -853,7 +853,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 1cd1de7..e8ea330 100644 --- a/modules/quat.lua +++ b/modules/quat.lua @@ -469,7 +469,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/vec2.lua b/modules/vec2.lua index cfc0653..d7a0786 100644 --- a/modules/vec2.lua +++ b/modules/vec2.lua @@ -381,7 +381,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 97b7f80..95c96eb 100644 --- a/modules/vec3.lua +++ b/modules/vec3.lua @@ -361,7 +361,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) From 13cc666232a23f3e9d29e15926fd65e530b33e82 Mon Sep 17 00:00:00 2001 From: mcc Date: Fri, 29 Nov 2019 22:24:47 -0500 Subject: [PATCH 5/9] Column version of to_vec4s (to_vec4s_cols) for issue #32 --- modules/mat4.lua | 14 ++++++++- spec/mat4_spec.lua | 74 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 70 insertions(+), 18 deletions(-) diff --git a/modules/mat4.lua b/modules/mat4.lua index 7db4c5a..9c136ee 100644 --- a/modules/mat4.lua +++ b/modules/mat4.lua @@ -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 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() From 8e65db07ce5eb353e48480023a59cba7ac4fb871 Mon Sep 17 00:00:00 2001 From: mcc Date: Sat, 30 Nov 2019 00:03:55 -0500 Subject: [PATCH 6/9] :round(precision) methods for vec2, vec3, bound2, bound3 Uses utils.lua, which requires moving utils.round to a new _private_utils.lua --- modules/_private_utils.lua | 11 +++++++++++ modules/bound2.lua | 8 ++++++++ modules/bound3.lua | 8 ++++++++ modules/utils.lua | 6 ++---- modules/vec2.lua | 9 +++++++++ modules/vec3.lua | 10 ++++++++++ spec/bound2_spec.lua | 9 +++++++++ spec/bound3_spec.lua | 11 +++++++++++ spec/vec2_spec.lua | 6 ++++++ spec/vec3_spec.lua | 7 +++++++ 10 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 modules/_private_utils.lua 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 8c049a4..319f8ed 100644 --- a/modules/bound2.lua +++ b/modules/bound2.lua @@ -152,6 +152,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 diff --git a/modules/bound3.lua b/modules/bound3.lua index 397640c..7f02ed2 100644 --- a/modules/bound3.lua +++ b/modules/bound3.lua @@ -152,6 +152,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 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..8347b16 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 diff --git a/modules/vec3.lua b/modules/vec3.lua index bf3d6bd..8e21caa 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 diff --git a/spec/bound2_spec.lua b/spec/bound2_spec.lua index a23322e..717ca1a 100644 --- a/spec/bound2_spec.lua +++ b/spec/bound2_spec.lua @@ -171,6 +171,15 @@ describe("bound2:", function() 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("checks for bound2.zero", function() assert.is.equal(0, bound2.zero.min.x) assert.is.equal(0, bound2.zero.min.y) diff --git a/spec/bound3_spec.lua b/spec/bound3_spec.lua index e3ae232..110159c 100644 --- a/spec/bound3_spec.lua +++ b/spec/bound3_spec.lua @@ -208,6 +208,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("checks for bound3.zero", function() assert.is.equal(0, bound3.zero.min.x) assert.is.equal(0, bound3.zero.min.y) diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index 8b826ad..29432bd 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -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) From e3f817bf7e198adcbcc791e9b42351a2d67155f1 Mon Sep 17 00:00:00 2001 From: mcc Date: Sat, 30 Nov 2019 01:24:48 -0500 Subject: [PATCH 7/9] extend(vec) and extend_bound(bound) for bounds expands bounds to cover new points --- modules/bound2.lua | 16 ++++++++++++++++ modules/bound3.lua | 16 ++++++++++++++++ spec/bound2_spec.lua | 36 ++++++++++++++++++++++++++++++++++++ spec/bound3_spec.lua | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) diff --git a/modules/bound2.lua b/modules/bound2.lua index 8c049a4..7a191bc 100644 --- a/modules/bound2.lua +++ b/modules/bound2.lua @@ -62,6 +62,22 @@ function bound2.at(a, b) -- "bounded by". b may be nil end end +--- Extend bound to include point +-- @tparam bound2 a bound +-- @tparam vec2 point to include +-- @treturn bound2 Bound covering current min, current max and new point +function bound2.extend(a, center) + return bound2.new(a.min:component_min(center), a.max:component_max(center)) +end + +--- Extend bound to entirety of other bound +-- @tparam bound2 a bound +-- @tparam bound2 bound to cover +-- @treturn bound2 Bound covering current min and max of each bound in the pair +function bound2.extend_bound(a, b) + return a:extend(b.min):extend(b.max) +end + --- Get size of bounding box as a vector -- @tparam bound2 a bound -- @treturn vec2 Vector spanning min to max points diff --git a/modules/bound3.lua b/modules/bound3.lua index 397640c..e6f450a 100644 --- a/modules/bound3.lua +++ b/modules/bound3.lua @@ -62,6 +62,22 @@ function bound3.at(a, b) -- "bounded by". b may be nil end end +--- Extend bound to include point +-- @tparam bound3 a bound +-- @tparam vec3 point to include +-- @treturn bound3 Bound covering current min, current max and new point +function bound3.extend(a, center) + return bound3.new(a.min:component_min(center), a.max:component_max(center)) +end + +--- Extend bound to entirety of other bound +-- @tparam bound3 a bound +-- @tparam bound3 bound to cover +-- @treturn bound3 Bound covering current min and max of each bound in the pair +function bound3.extend_bound(a, b) + return a:extend(b.min):extend(b.max) +end + --- Get size of bounding box as a vector -- @tparam bound3 a bound -- @treturn vec3 Vector spanning min to max points diff --git a/spec/bound2_spec.lua b/spec/bound2_spec.lua index a23322e..5d2987e 100644 --- a/spec/bound2_spec.lua +++ b/spec/bound2_spec.lua @@ -171,6 +171,42 @@ describe("bound2:", function() assert.is_not_true(a:contains(vec2(2,3))) end) + it("extends a bound2 with a point", function() + local min = vec2(1,2) + local max = vec2(4,5) + local downright = vec2(8,8) + local downleft = vec2(-4,8) + local top = vec2(2, 0) + + local a = bound2(min, max) + local temp + + temp = a:extend(downright) + assert.is_true(a.min == min and a.max == max) + assert.is_true(temp.min == min and temp.max == downright) + temp = a:extend(downleft) + assert.is_true(temp.min == vec2(-4,2) and temp.max == vec2(4,8)) + temp = a:extend(top) + assert.is_true(temp.min == vec2(1,0) and temp.max == max) + end) + + it("extends a bound with another bound", function() + local min = vec2(1,2) + local max = vec2(4,5) + local leftexpand = bound2.new(vec2(0,0), vec2(1.5, 6)) + local rightexpand = bound2.new(vec2(1.5,0), vec2(5, 6)) + + local a = bound2(min, max) + local temp + + temp = a:extend_bound(leftexpand) + assert.is_equal(temp.min, vec2(0,0)) + assert.is_equal(temp.max, vec2(4,6)) + temp = temp:extend_bound(rightexpand) + assert.is_equal(temp.min, vec2(0,0)) + assert.is_equal(temp.max, vec2(5,6)) + end) + it("checks for bound2.zero", function() assert.is.equal(0, bound2.zero.min.x) assert.is.equal(0, bound2.zero.min.y) diff --git a/spec/bound3_spec.lua b/spec/bound3_spec.lua index e3ae232..5e54d01 100644 --- a/spec/bound3_spec.lua +++ b/spec/bound3_spec.lua @@ -208,6 +208,42 @@ describe("bound3:", function() assert.is_not_true(a:contains(vec3(2,3,7))) end) + it("extends a bound3 with a point", function() + local min = vec3(1,2,6) + local max = vec3(4,5,9) + local downright = vec3(8,8,10) + local downleft = vec3(-4,8,10) + local top = vec3(2, 0, 7) + + local a = bound3(min, max) + local temp + + temp = a:extend(downright) + assert.is_true(a.min == min and a.max == max) + assert.is_true(temp.min == min and temp.max == downright) + temp = a:extend(downleft) + assert.is_true(temp.min == vec3(-4,2,6) and temp.max == vec3(4,8,10)) + temp = a:extend(top) + assert.is_true(temp.min == vec3(1,0,6) and temp.max == max) + end) + + it("extends a bound with another bound", function() + local min = vec3(1,2,3) + local max = vec3(4,5,6) + local leftexpand = bound3.new(vec3(0,0,4), vec3(1.5,6,5)) + local rightexpand = bound3.new(vec3(1.5,0,1), vec3(5,6,7)) + + local a = bound3(min, max) + local temp + + temp = a:extend_bound(leftexpand) + assert.is_equal(temp.min, vec3(0,0,3)) + assert.is_equal(temp.max, vec3(4,6,6)) + temp = temp:extend_bound(rightexpand) + assert.is_equal(temp.min, vec3(0,0,1)) + assert.is_equal(temp.max, vec3(5,6,7)) + end) + it("checks for bound3.zero", function() assert.is.equal(0, bound3.zero.min.x) assert.is.equal(0, bound3.zero.min.y) From 7452cc0c6c15dc890d45570b297236874dda0fdc Mon Sep 17 00:00:00 2001 From: mcc Date: Sat, 30 Nov 2019 11:25:00 -0500 Subject: [PATCH 8/9] Simple methods for flipping a vector on exactly 1 axis --- modules/vec2.lua | 14 ++++++++++++++ modules/vec3.lua | 21 +++++++++++++++++++++ spec/vec2_spec.lua | 8 ++++++++ spec/vec3_spec.lua | 10 ++++++++++ 4 files changed, 53 insertions(+) diff --git a/modules/vec2.lua b/modules/vec2.lua index 01a0b97..6b8fe68 100644 --- a/modules/vec2.lua +++ b/modules/vec2.lua @@ -321,6 +321,20 @@ function vec2.to_polar(a) return radius, theta end +-- Negate x axis only of vector. +-- @tparam vec2 a Vector to x-flip. +-- @treturn vec2 x-flipped vector +function vec2.flip_x(a) + return vec2.new(-a.x, a.y) +end + +-- Negate y axis only of vector. +-- @tparam vec2 a Vector to y-flip. +-- @treturn vec2 y-flipped vector +function vec2.flip_y(a) + return vec2.new(a.x, -a.y) +end + --- Return a formatted string. -- @tparam vec2 a Vector to be turned into a string -- @treturn string formatted diff --git a/modules/vec3.lua b/modules/vec3.lua index bf3d6bd..e7bf90b 100644 --- a/modules/vec3.lua +++ b/modules/vec3.lua @@ -279,6 +279,27 @@ function vec3.component_max(a, b) return new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z)) end +-- Negate x axis only of vector. +-- @tparam vec2 a Vector to x-flip. +-- @treturn vec2 x-flipped vector +function vec3.flip_x(a) + return vec3.new(-a.x, a.y, a.z) +end + +-- Negate y axis only of vector. +-- @tparam vec2 a Vector to y-flip. +-- @treturn vec2 y-flipped vector +function vec3.flip_y(a) + return vec3.new(a.x, -a.y, a.z) +end + +-- Negate z axis only of vector. +-- @tparam vec2 a Vector to z-flip. +-- @treturn vec2 z-flipped vector +function vec3.flip_z(a) + return vec3.new(a.x, a.y, -a.z) +end + --- Return a boolean showing if a table is or is not a vec3. -- @tparam vec3 a Vector to be tested -- @treturn boolean is_vec3 diff --git a/spec/vec2_spec.lua b/spec/vec2_spec.lua index 8b826ad..57b3f53 100644 --- a/spec/vec2_spec.lua +++ b/spec/vec2_spec.lua @@ -189,6 +189,14 @@ describe("vec2:", function() assert.is.equal("(+0.000,+0.000)", b) end) + it("flips a 2-vector", function() + local a = vec2(1,2) + local temp = a:flip_x() + assert.is.equal(temp, vec2(-1, 2)) + temp = temp:flip_y() + assert.is.equal(temp, vec2(-1, -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..96f4b89 100644 --- a/spec/vec3_spec.lua +++ b/spec/vec3_spec.lua @@ -202,4 +202,14 @@ describe("vec3:", function() local b = a:to_string() assert.is.equal("(+0.000,+0.000,+0.000)", b) end) + + it("flips a 3-vector", function() + local a = vec3(1,2,3) + local temp = a:flip_x() + assert.is.equal(temp, vec3(-1, 2, 3)) + temp = temp:flip_y() + assert.is.equal(temp, vec3(-1, -2, 3)) + temp = temp:flip_z() + assert.is.equal(temp, vec3(-1, -2, -3)) + end) end) From 4cb051614db90a3705c60c8447a3b6fe0da2637a Mon Sep 17 00:00:00 2001 From: mcc Date: Sat, 30 Nov 2019 11:41:48 -0500 Subject: [PATCH 9/9] Include BSD-2-clause text in LICENSE.md --- LICENSE.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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.