Add new vector utils (ceil, sign, abs, random_in_area) (#14807)

This commit is contained in:
kromka-chleba 2024-08-31 09:43:52 +00:00 committed by GitHub
parent 3971b6afcc
commit a6ba5304c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 143 additions and 52 deletions

View File

@ -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"] = {

41
builtin/common/math.lua Normal file
View File

@ -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

View File

@ -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 = {
["\\"] = "\\\\",
["["] = "\\[",

View File

@ -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")

View File

@ -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)

View File

@ -1,4 +1,5 @@
_G.core = {}
dofile("builtin/common/math.lua")
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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: