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 8c049a4..7252b99 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 @@ -152,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 @@ -167,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 397640c..c5c9751 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 @@ -152,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 @@ -167,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 030937f..ca153d9 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,28 @@ 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 + +-- 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 + -- Convert vec2 to vec3. -- @tparam vec2 a Vector to convert. -- @tparam number the new z component, or nil for 0 @@ -389,7 +412,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..032b596 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 @@ -279,6 +289,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 @@ -361,7 +392,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 a23322e..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,15 +162,60 @@ 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() + 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..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,53 @@ 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) + 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) 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 4bd0305..f79c807 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,21 @@ 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) + + 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 -- Do vec3 tests last, to insulate tests from accidental state contamination it("converts a 2-vector to a 3-vector", function() local vec3 = require "modules.vec3" diff --git a/spec/vec3_spec.lua b/spec/vec3_spec.lua index 79eca55..bd2659a 100644 --- a/spec/vec3_spec.lua +++ b/spec/vec3_spec.lua @@ -202,4 +202,21 @@ 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) + + 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)