diff --git a/.luacheckrc b/.luacheckrc index f184e6d59..afc136c7c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -37,6 +37,12 @@ files["builtin/client/register.lua"] = { } } +files["builtin/common/math.lua"] = { + globals = { + "math", + }, +} + files["builtin/common/misc_helpers.lua"] = { globals = { "dump", "dump2", "table", "math", "string", @@ -46,7 +52,7 @@ files["builtin/common/misc_helpers.lua"] = { } files["builtin/common/vector.lua"] = { - globals = { "vector" }, + globals = { "vector", "math" }, } files["builtin/game/voxelarea.lua"] = { diff --git a/builtin/common/math.lua b/builtin/common/math.lua new file mode 100644 index 000000000..84c6c619b --- /dev/null +++ b/builtin/common/math.lua @@ -0,0 +1,41 @@ +--[[ + Math utils. +--]] + +function math.hypot(x, y) + return math.sqrt(x * x + y * y) +end + +function math.sign(x, tolerance) + tolerance = tolerance or 0 + if x > tolerance then + return 1 + elseif x < -tolerance then + return -1 + end + return 0 +end + +function math.factorial(x) + assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer") + if x >= 171 then + -- 171! is greater than the biggest double, no need to calculate + return math.huge + end + local v = 1 + for k = 2, x do + v = v * k + end + return v +end + +function math.round(x) + if x < 0 then + local int = math.ceil(x) + local frac = x - int + return int - ((frac <= -0.5) and 1 or 0) + end + local int = math.floor(x) + local frac = x - int + return int + ((frac >= 0.5) and 1 or 0) +end diff --git a/builtin/common/misc_helpers.lua b/builtin/common/misc_helpers.lua index 21752ce7f..9c25b826f 100644 --- a/builtin/common/misc_helpers.lua +++ b/builtin/common/misc_helpers.lua @@ -3,6 +3,7 @@ -------------------------------------------------------------------------------- -- Localize functions to avoid table lookups (better performance). local string_sub, string_find = string.sub, string.find +local math = math -------------------------------------------------------------------------------- local function basic_dump(o) @@ -220,47 +221,6 @@ function string:trim() return self:match("^%s*(.-)%s*$") end --------------------------------------------------------------------------------- -function math.hypot(x, y) - return math.sqrt(x * x + y * y) -end - --------------------------------------------------------------------------------- -function math.sign(x, tolerance) - tolerance = tolerance or 0 - if x > tolerance then - return 1 - elseif x < -tolerance then - return -1 - end - return 0 -end - --------------------------------------------------------------------------------- -function math.factorial(x) - assert(x % 1 == 0 and x >= 0, "factorial expects a non-negative integer") - if x >= 171 then - -- 171! is greater than the biggest double, no need to calculate - return math.huge - end - local v = 1 - for k = 2, x do - v = v * k - end - return v -end - -function math.round(x) - if x < 0 then - local int = math.ceil(x) - local frac = x - int - return int - ((frac <= -0.5) and 1 or 0) - end - local int = math.floor(x) - local frac = x - int - return int + ((frac >= 0.5) and 1 or 0) -end - local formspec_escapes = { ["\\"] = "\\\\", ["["] = "\\[", diff --git a/builtin/common/tests/after_spec.lua b/builtin/common/tests/after_spec.lua index cca1871a5..f23bbd70d 100644 --- a/builtin/common/tests/after_spec.lua +++ b/builtin/common/tests/after_spec.lua @@ -1,6 +1,7 @@ _G.core = {} _G.vector = {metatable = {}} +dofile("builtin/common/math.lua") dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") diff --git a/builtin/common/tests/math_spec.lua b/builtin/common/tests/math_spec.lua new file mode 100644 index 000000000..108ab2b6a --- /dev/null +++ b/builtin/common/tests/math_spec.lua @@ -0,0 +1,16 @@ +_G.core = {} +dofile("builtin/common/math.lua") + +describe("math", function() + it("round()", function() + assert.equal(0, math.round(0)) + assert.equal(10, math.round(10.3)) + assert.equal(11, math.round(10.5)) + assert.equal(11, math.round(10.7)) + assert.equal(-10, math.round(-10.3)) + assert.equal(-11, math.round(-10.5)) + assert.equal(-11, math.round(-10.7)) + assert.equal(0, math.round(0.49999999999999994)) + assert.equal(0, math.round(-0.49999999999999994)) + end) +end) diff --git a/builtin/common/tests/misc_helpers_spec.lua b/builtin/common/tests/misc_helpers_spec.lua index 611c4b20f..10e2bf277 100644 --- a/builtin/common/tests/misc_helpers_spec.lua +++ b/builtin/common/tests/misc_helpers_spec.lua @@ -1,4 +1,5 @@ _G.core = {} +dofile("builtin/common/math.lua") dofile("builtin/common/vector.lua") dofile("builtin/common/misc_helpers.lua") diff --git a/builtin/common/tests/vector_spec.lua b/builtin/common/tests/vector_spec.lua index 67dbd2d6b..9a0458be4 100644 --- a/builtin/common/tests/vector_spec.lua +++ b/builtin/common/tests/vector_spec.lua @@ -1,4 +1,5 @@ _G.vector = {} +dofile("builtin/common/math.lua") dofile("builtin/common/vector.lua") describe("vector", function() @@ -113,12 +114,35 @@ describe("vector", function() assert.equal(vector.new(0, 1, -1), a:round()) end) + it("ceil()", function() + local a = vector.new(0.1, 0.9, -0.5) + assert.equal(vector.new(1, 1, 0), vector.ceil(a)) + assert.equal(vector.new(1, 1, 0), a:ceil()) + end) + + it("sign()", function() + local a = vector.new(-120.3, 0, 231.5) + assert.equal(vector.new(-1, 0, 1), vector.sign(a)) + assert.equal(vector.new(-1, 0, 1), a:sign()) + assert.equal(vector.new(0, 0, 1), vector.sign(a, 200)) + assert.equal(vector.new(0, 0, 1), a:sign(200)) + end) + + it("abs()", function() + local a = vector.new(-123.456, 0, 13) + assert.equal(vector.new(123.456, 0, 13), vector.abs(a)) + assert.equal(vector.new(123.456, 0, 13), a:abs()) + end) + it("apply()", function() local i = 0 local f = function(x) i = i + 1 return x + i end + local f2 = function(x, opt1, opt2, opt3) + return x + opt1 + opt2 + opt3 + end local a = vector.new(0.1, 0.9, -0.5) assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil)) assert.equal(vector.new(1, 1, 0), a:apply(math.ceil)) @@ -126,6 +150,9 @@ describe("vector", function() assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs)) assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f)) assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f)) + local b = vector.new(1, 2, 3) + assert.equal(vector.new(4, 5, 6), vector.apply(b, f2, 1, 1, 1)) + assert.equal(vector.new(4, 5, 6), b:apply(f2, 1, 1, 1)) end) it("combine()", function() @@ -469,4 +496,13 @@ describe("vector", function() assert.True(vector.in_area(vector.new(-10, -10, -10), vector.new(-10, -10, -10), vector.new(10, 10, 10))) assert.False(vector.in_area(vector.new(-10, -10, -10), vector.new(10, 10, 10), vector.new(-11, -10, -10))) end) + + it("random_in_area()", function() + local min = vector.new(-100, -100, -100) + local max = vector.new(100, 100, 100) + for i = 1, 1000 do + local random = vector.random_in_area(min, max) + assert.True(vector.in_area(random, min, max)) + end + end) end) diff --git a/builtin/common/vector.lua b/builtin/common/vector.lua index be82401c3..fb1d2a7d9 100644 --- a/builtin/common/vector.lua +++ b/builtin/common/vector.lua @@ -5,6 +5,7 @@ Note: The vector.*-functions must be able to accept old vectors that had no meta -- localize functions local setmetatable = setmetatable +local math = math vector = {} @@ -97,18 +98,26 @@ function vector.floor(v) end function vector.round(v) - return fast_new( - math.round(v.x), - math.round(v.y), - math.round(v.z) - ) + return vector.apply(v, math.round) end -function vector.apply(v, func) +function vector.ceil(v) + return vector.apply(v, math.ceil) +end + +function vector.sign(v, tolerance) + return vector.apply(v, math.sign, tolerance) +end + +function vector.abs(v) + return vector.apply(v, math.abs) +end + +function vector.apply(v, func, ...) return fast_new( - func(v.x), - func(v.y), - func(v.z) + func(v.x, ...), + func(v.y, ...), + func(v.z, ...) ) end @@ -387,6 +396,14 @@ function vector.random_direction() return fast_new(x/l, y/l, z/l) end +function vector.random_in_area(min, max) + return fast_new( + math.random(min.x, max.x), + math.random(min.y, max.y), + math.random(min.z, max.z) + ) +end + if rawget(_G, "core") and core.set_read_vector and core.set_push_vector then local function read_vector(v) return v.x, v.y, v.z diff --git a/builtin/init.lua b/builtin/init.lua index 49df70971..415087a54 100644 --- a/builtin/init.lua +++ b/builtin/init.lua @@ -42,6 +42,7 @@ local scriptdir = core.get_builtin_path() local commonpath = scriptdir .. "common" .. DIR_DELIM local asyncpath = scriptdir .. "async" .. DIR_DELIM +dofile(commonpath .. "math.lua") dofile(commonpath .. "vector.lua") dofile(commonpath .. "strict.lua") dofile(commonpath .. "serialize.lua") diff --git a/doc/lua_api.md b/doc/lua_api.md index ae046dd3a..bf714a515 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -3850,12 +3850,20 @@ vectors are written like this: `(x, y, z)`: * If `v` has zero length, returns `(0, 0, 0)`. * `vector.floor(v)`: * Returns a vector, each dimension rounded down. +* `vector.ceil(v)`: + * Returns a vector, each dimension rounded up. * `vector.round(v)`: * Returns a vector, each dimension rounded to nearest integer. * At a multiple of 0.5, rounds away from zero. -* `vector.apply(v, func)`: +* `vector.sign(v, tolerance)`: + * Returns a vector where `math.sign` was called for each component. + * See [Helper functions] for details. +* `vector.abs(v)`: + * Returns a vector with absolute values for each component. +* `vector.apply(v, func, ...)`: * Returns a vector where the function `func` has been applied to each component. + * `...` are optional arguments passed to `func`. * `vector.combine(v, w, func)`: * Returns a vector where the function `func` has combined both components of `v` and `w` for each component @@ -3880,6 +3888,10 @@ vectors are written like this: `(x, y, z)`: * `min` and `max` are inclusive. * If `min` is bigger than `max` on some axis, function always returns false. * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum. +* `vector.random_in_area(min, max)`: + * Returns a random integer position in area formed by `min` and `max` + * `min` and `max` are inclusive. + * You can use `vector.sort` if you have two vectors and don't know which are the minimum and the maximum. For the following functions `x` can be either a vector or a number: