mirror of
https://github.com/luanti-org/luanti.git
synced 2026-04-17 08:37:33 +00:00
Add vector2 lua API for 2D vectors (#16929)
This commit is contained in:
+6
-1
@@ -15,6 +15,7 @@ read_globals = {
|
||||
"dump", "dump2",
|
||||
"fgettext", "fgettext_ne",
|
||||
"vector",
|
||||
"vector2",
|
||||
"VoxelArea",
|
||||
"VoxelManip",
|
||||
"profiler",
|
||||
@@ -24,7 +25,7 @@ read_globals = {
|
||||
|
||||
string = {fields = {"split", "trim"}},
|
||||
table = {fields = {"copy", "copy_with_metatables", "getn", "indexof", "keyof", "insert_all", "shuffle"}},
|
||||
math = {fields = {"hypot", "round", "isfinite"}},
|
||||
math = {fields = {"hypot", "round", "isfinite", "sign"}},
|
||||
}
|
||||
|
||||
globals = {
|
||||
@@ -71,6 +72,10 @@ files["builtin/common/vector.lua"] = {
|
||||
globals = { "vector", "math" },
|
||||
}
|
||||
|
||||
files["builtin/common/vector2.lua"] = {
|
||||
globals = { "vector2", "math" },
|
||||
}
|
||||
|
||||
files["builtin/game/voxelarea.lua"] = {
|
||||
globals = { "VoxelArea" },
|
||||
}
|
||||
|
||||
@@ -0,0 +1,401 @@
|
||||
_G.vector = {}
|
||||
_G.vector2 = {}
|
||||
dofile("builtin/common/math.lua")
|
||||
dofile("builtin/common/vector.lua")
|
||||
dofile("builtin/common/vector2.lua")
|
||||
|
||||
-- Custom assertion for comparing floating-point numbers with tolerance
|
||||
local function number_close(state, arguments)
|
||||
if #arguments < 2 then
|
||||
return false
|
||||
end
|
||||
|
||||
local expected = arguments[1]
|
||||
local actual = arguments[2]
|
||||
local tolerance = arguments[3] or 0.000001
|
||||
|
||||
if type(expected) == "number" and type(actual) == "number" then
|
||||
return math.abs(expected - actual) < tolerance
|
||||
end
|
||||
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Custom assertion for comparing vectors with tolerance
|
||||
-- Uses component-wise comparison to be self-contained
|
||||
local function vector2_close(state, arguments)
|
||||
if #arguments < 2 then
|
||||
return false
|
||||
end
|
||||
|
||||
local expected = arguments[1]
|
||||
local actual = arguments[2]
|
||||
local tolerance = arguments[3] or 0.000001
|
||||
|
||||
if type(expected) == "table" and type(actual) == "table" then
|
||||
return math.abs(expected.x - actual.x) < tolerance and
|
||||
math.abs(expected.y - actual.y) < tolerance
|
||||
end
|
||||
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
assert:register("assertion", "number_close", number_close)
|
||||
assert:register("assertion", "vector2_close", vector2_close)
|
||||
|
||||
describe("vector2", function()
|
||||
describe("new()", function()
|
||||
it("constructs", function()
|
||||
assert.same({x = 1, y = 2}, vector2.new(1, 2))
|
||||
|
||||
assert.is_true(vector2.check(vector2.new(1, 2)))
|
||||
end)
|
||||
|
||||
it("throws on invalid input", function()
|
||||
assert.has.errors(function()
|
||||
vector2.new()
|
||||
end)
|
||||
|
||||
assert.has.errors(function()
|
||||
vector2.new({ x = 3, y = 2 })
|
||||
end)
|
||||
|
||||
assert.has.errors(function()
|
||||
vector2.new({ x = 3 })
|
||||
end)
|
||||
|
||||
assert.has.errors(function()
|
||||
vector2.new({ d = 3 })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
it("zero()", function()
|
||||
assert.same({x = 0, y = 0}, vector2.zero())
|
||||
assert.is_true(vector2.check(vector2.zero()))
|
||||
end)
|
||||
|
||||
it("copy()", function()
|
||||
local v = vector2.new(1, 2)
|
||||
assert.same(v, vector2.copy(v))
|
||||
assert.is_true(vector2.check(vector2.copy(v)))
|
||||
end)
|
||||
|
||||
it("indexes", function()
|
||||
local some_vector = vector2.new(24, 42)
|
||||
assert.equal(24, some_vector[1])
|
||||
assert.equal(24, some_vector.x)
|
||||
assert.equal(42, some_vector[2])
|
||||
assert.equal(42, some_vector.y)
|
||||
|
||||
some_vector[1] = 100
|
||||
assert.equal(100, some_vector.x)
|
||||
some_vector.x = 101
|
||||
assert.equal(101, some_vector[1])
|
||||
|
||||
some_vector[2] = 100
|
||||
assert.equal(100, some_vector.y)
|
||||
some_vector.y = 102
|
||||
assert.equal(102, some_vector[2])
|
||||
end)
|
||||
|
||||
it("direction()", function()
|
||||
local a = vector2.new(1, 0)
|
||||
local b = vector2.new(1, 42)
|
||||
local dir1 = vector2.direction(a, b)
|
||||
assert.number_close(0, dir1.x)
|
||||
assert.number_close(1, dir1.y)
|
||||
local dir2 = a:direction(b)
|
||||
assert.number_close(0, dir2.x)
|
||||
assert.number_close(1, dir2.y)
|
||||
end)
|
||||
|
||||
it("distance()", function()
|
||||
local a = vector2.new(1, 0)
|
||||
local b = vector2.new(4, 4)
|
||||
assert.number_close(5, vector2.distance(a, b))
|
||||
assert.number_close(5, a:distance(b))
|
||||
assert.number_close(0, vector2.distance(a, a))
|
||||
assert.number_close(0, b:distance(b))
|
||||
end)
|
||||
|
||||
it("length()", function()
|
||||
local a = vector2.new(3, 4)
|
||||
assert.number_close(0, vector2.length(vector2.zero()))
|
||||
assert.number_close(5, vector2.length(a))
|
||||
assert.number_close(5, a:length())
|
||||
end)
|
||||
|
||||
it("normalize()", function()
|
||||
local a = vector2.new(0, -5)
|
||||
local norm1 = vector2.normalize(a)
|
||||
assert.number_close(0, norm1.x)
|
||||
assert.number_close(-1, norm1.y)
|
||||
local norm2 = a:normalize()
|
||||
assert.number_close(0, norm2.x)
|
||||
assert.number_close(-1, norm2.y)
|
||||
local norm3 = vector2.normalize(vector2.zero())
|
||||
assert.number_close(0, norm3.x)
|
||||
assert.number_close(0, norm3.y)
|
||||
end)
|
||||
|
||||
it("floor()", function()
|
||||
local a = vector2.new(0.1, 0.9)
|
||||
assert.same(vector2.new(0, 0), vector2.floor(a))
|
||||
assert.same(vector2.new(0, 0), a:floor())
|
||||
end)
|
||||
|
||||
it("round()", function()
|
||||
local a = vector2.new(0.1, 0.9)
|
||||
assert.same(vector2.new(0, 1), vector2.round(a))
|
||||
assert.same(vector2.new(0, 1), a:round())
|
||||
end)
|
||||
|
||||
it("ceil()", function()
|
||||
local a = vector2.new(0.1, 0.9)
|
||||
assert.same(vector2.new(1, 1), vector2.ceil(a))
|
||||
assert.same(vector2.new(1, 1), a:ceil())
|
||||
end)
|
||||
|
||||
it("sign()", function()
|
||||
local a = vector2.new(-120.3, 231.5)
|
||||
assert.same(vector2.new(-1, 1), vector2.sign(a))
|
||||
assert.same(vector2.new(-1, 1), a:sign())
|
||||
assert.same(vector2.new(0, 1), vector2.sign(a, 200))
|
||||
assert.same(vector2.new(0, 1), a:sign(200))
|
||||
end)
|
||||
|
||||
it("abs()", function()
|
||||
local a = vector2.new(-123.456, 13)
|
||||
assert.same(vector2.new(123.456, 13), vector2.abs(a))
|
||||
assert.same(vector2.new(123.456, 13), a:abs())
|
||||
end)
|
||||
|
||||
it("apply()", function()
|
||||
local f = function(x)
|
||||
return x * 2
|
||||
end
|
||||
local f2 = function(x, opt1, opt2)
|
||||
return x + opt1 + opt2
|
||||
end
|
||||
local a = vector2.new(0.1, 0.9)
|
||||
assert.same(vector2.new(1, 1), vector2.apply(a, math.ceil))
|
||||
assert.same(vector2.new(1, 1), a:apply(math.ceil))
|
||||
assert.same(vector2.new(0.1, 0.9), vector2.apply(a, math.abs))
|
||||
assert.same(vector2.new(0.1, 0.9), a:apply(math.abs))
|
||||
assert.same(vector2.new(0.2, 1.8), vector2.apply(a, f))
|
||||
assert.same(vector2.new(0.2, 1.8), a:apply(f))
|
||||
local b = vector2.new(1, 2)
|
||||
assert.same(vector2.new(3, 4), vector2.apply(b, f2, 1, 1))
|
||||
assert.same(vector2.new(3, 4), b:apply(f2, 1, 1))
|
||||
end)
|
||||
|
||||
it("combine()", function()
|
||||
local a = vector2.new(1, 4)
|
||||
local b = vector2.new(2, 3)
|
||||
assert.same(vector2.add(a, b), vector2.combine(a, b, function(x, y) return x + y end))
|
||||
assert.same(vector2.new(2, 4), vector2.combine(a, b, math.max))
|
||||
assert.same(vector2.new(1, 3), vector2.combine(a, b, math.min))
|
||||
end)
|
||||
|
||||
it("equals()", function()
|
||||
assert.is_true(vector2.equals({x = 0, y = 0}, {x = 0, y = 0}))
|
||||
assert.is_true(vector2.equals({x = -1, y = 0}, vector2.new(-1, 0)))
|
||||
assert.is_false(vector2.equals({x = 1, y = 2}, {x = 1, y = 3}))
|
||||
local a = vector2.new(1, 2)
|
||||
assert.is_true(a:equals(a))
|
||||
assert.is_true(vector2.new(1, 2) == vector2.new(1, 2))
|
||||
assert.is_false(vector2.new(1, 2) == vector2.new(1, 3))
|
||||
end)
|
||||
|
||||
it("metatable is same", function()
|
||||
local a = vector2.zero()
|
||||
local b = vector2.new(1, 2)
|
||||
|
||||
assert.equal(true, vector2.check(a))
|
||||
assert.equal(true, vector2.check(b))
|
||||
|
||||
assert.equal(vector2.metatable, getmetatable(a))
|
||||
assert.equal(vector2.metatable, getmetatable(b))
|
||||
assert.equal(vector2.metatable, a.metatable)
|
||||
end)
|
||||
|
||||
it("sort()", function()
|
||||
local a = vector2.new(1, 2)
|
||||
local b = vector2.new(0.5, 232)
|
||||
local sorted = {vector2.new(0.5, 2), vector2.new(1, 232)}
|
||||
assert.same(sorted, {vector2.sort(a, b)})
|
||||
assert.same(sorted, {a:sort(b)})
|
||||
end)
|
||||
|
||||
it("angle()", function()
|
||||
assert.number_close(math.pi, vector2.angle(vector2.new(-1, -2), vector2.new(1, 2)))
|
||||
assert.number_close(math.pi/2, vector2.new(0, 1):angle(vector2.new(1, 0)))
|
||||
end)
|
||||
|
||||
it("dot()", function()
|
||||
assert.equal(-5, vector2.dot(vector2.new(-1, -2), vector2.new(1, 2)))
|
||||
assert.equal(0, vector2.zero():dot(vector2.new(1, 2)))
|
||||
end)
|
||||
|
||||
it("offset()", function()
|
||||
assert.same({x = 41, y = 52}, vector2.offset(vector2.new(1, 2), 40, 50))
|
||||
assert.same(vector2.new(41, 52), vector2.offset(vector2.new(1, 2), 40, 50))
|
||||
assert.same(vector2.new(41, 52), vector2.new(1, 2):offset(40, 50))
|
||||
end)
|
||||
|
||||
it("check()", function()
|
||||
assert.is_false(vector2.check(nil))
|
||||
assert.is_false(vector2.check(1))
|
||||
assert.is_false(vector2.check({x = 1, y = 2}))
|
||||
local real = vector2.new(1, 2)
|
||||
assert.is_true(vector2.check(real))
|
||||
assert.is_true(real:check())
|
||||
end)
|
||||
|
||||
it("abusing works", function()
|
||||
local v = vector2.new(1, 2)
|
||||
v.a = 1
|
||||
assert.equal(1, v.a)
|
||||
|
||||
local a_is_there = false
|
||||
for key, value in pairs(v) do
|
||||
if key == "a" then
|
||||
a_is_there = true
|
||||
assert.equal(value, 1)
|
||||
break
|
||||
end
|
||||
end
|
||||
assert.is_true(a_is_there)
|
||||
end)
|
||||
|
||||
it("add()", function()
|
||||
local a = vector2.new(1, 2)
|
||||
local b = vector2.new(1, 4)
|
||||
local c = vector2.new(2, 6)
|
||||
assert.same(c, vector2.add(a, {x = 1, y = 4}))
|
||||
assert.same(c, vector2.add(a, b))
|
||||
assert.same(c, a:add(b))
|
||||
assert.same(c, a + b)
|
||||
assert.same(c, b + a)
|
||||
end)
|
||||
|
||||
it("subtract()", function()
|
||||
local a = vector2.new(1, 2)
|
||||
local b = vector2.new(2, 4)
|
||||
local c = vector2.new(-1, -2)
|
||||
assert.same(c, vector2.subtract(a, {x = 2, y = 4}))
|
||||
assert.same(c, vector2.subtract(a, b))
|
||||
assert.same(c, a:subtract(b))
|
||||
assert.same(c, a - b)
|
||||
assert.same(c, -b + a)
|
||||
end)
|
||||
|
||||
it("multiply()", function()
|
||||
local a = vector2.new(1, 2)
|
||||
local s = 2
|
||||
local d = vector2.new(2, 4)
|
||||
assert.same(d, vector2.multiply(a, s))
|
||||
assert.same(d, a:multiply(s))
|
||||
assert.same(d, a * s)
|
||||
assert.same(d, s * a)
|
||||
assert.same(-a, -1 * a)
|
||||
end)
|
||||
|
||||
it("divide()", function()
|
||||
local a = vector2.new(1, 2)
|
||||
local s = 2
|
||||
local d = vector2.new(0.5, 1)
|
||||
assert.same(d, vector2.divide(a, s))
|
||||
assert.same(d, a:divide(s))
|
||||
assert.same(d, a / s)
|
||||
assert.same(d, 1/s * a)
|
||||
assert.same(-a, a / -1)
|
||||
end)
|
||||
|
||||
it("to_string()", function()
|
||||
local v = vector2.new(1, 2)
|
||||
local str1 = vector2.to_string(v)
|
||||
local str2 = v:to_string()
|
||||
local str3 = tostring(v)
|
||||
|
||||
-- All should produce the same string
|
||||
assert.same(str1, str2)
|
||||
assert.same(str1, str3)
|
||||
|
||||
-- Verify the string format
|
||||
assert.same("(1, 2)", str1)
|
||||
|
||||
-- Test edge cases for %g format
|
||||
assert.same("(0, 0)", vector2.to_string(vector2.new(0, 0)))
|
||||
assert.same("(-1, -2)", vector2.to_string(vector2.new(-1, -2)))
|
||||
assert.same("(0.0001, 1e+10)", vector2.to_string(vector2.new(0.0001, 1e10)))
|
||||
assert.same("(3.14159, 1.41421)", vector2.to_string(vector2.new(math.pi, math.sqrt(2))))
|
||||
end)
|
||||
|
||||
it("from_string()", function()
|
||||
local v = vector2.new(1, 2)
|
||||
assert.is_true(vector2.check(vector2.from_string("(1, 2)")))
|
||||
assert.same({v, 7}, {vector2.from_string("(1, 2)")})
|
||||
assert.same({v, 7}, {vector2.from_string("(1,2 )")})
|
||||
assert.same({v, 7}, {vector2.from_string("(1,2,)")})
|
||||
assert.same({v, 6}, {vector2.from_string("(1 2)")})
|
||||
assert.same({v, 9}, {vector2.from_string("( 1, 2 )")})
|
||||
assert.same({v, 9}, {vector2.from_string(" ( 1, 2) ")})
|
||||
assert.same({vector2.zero(), 6}, {vector2.from_string("(0,0) ( 1, 2) ")})
|
||||
assert.same({v, 14}, {vector2.from_string("(0,0) ( 1, 2) ", 6)})
|
||||
assert.same({v, 14}, {vector2.from_string("(0,0) ( 1, 2) ", 7)})
|
||||
assert.is_nil(vector2.from_string("nothing"))
|
||||
end)
|
||||
|
||||
describe("from_angle()", function()
|
||||
it("creates unit vector from angle", function()
|
||||
assert.vector2_close(vector2.new(1, 0), vector2.from_angle(0))
|
||||
assert.vector2_close(vector2.new(0, 1), vector2.from_angle(math.pi / 2))
|
||||
assert.vector2_close(vector2.new(-1, 0), vector2.from_angle(math.pi))
|
||||
end)
|
||||
|
||||
it("throws on invalid input", function()
|
||||
assert.has.errors(function()
|
||||
vector2.from_angle()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("to_angle()", function()
|
||||
it("returns angle of vector", function()
|
||||
assert.number_close(0, vector2.to_angle(vector2.new(1, 0)))
|
||||
assert.number_close(math.pi / 2, vector2.to_angle(vector2.new(0, 1)))
|
||||
assert.number_close(math.pi / 4, vector2.to_angle(vector2.new(1, 1)))
|
||||
end)
|
||||
|
||||
it("is inverse of from_angle", function()
|
||||
local angle = math.pi / 3
|
||||
local v = vector2.from_angle(angle)
|
||||
assert.number_close(angle, vector2.to_angle(v))
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("rotate()", function()
|
||||
it("rotates vector by angle in radians", function()
|
||||
assert.vector2_close(vector2.new(0, 1), vector2.rotate(vector2.new(1, 0), math.pi / 2))
|
||||
assert.vector2_close(vector2.new(-1, 0), vector2.rotate(vector2.new(1, 0), math.pi))
|
||||
assert.vector2_close(vector2.new(0, 1), vector2.new(1, 0):rotate(math.pi / 2))
|
||||
end)
|
||||
|
||||
it("preserves length", function()
|
||||
local v = vector2.new(3, 4)
|
||||
local rotated = vector2.rotate(v, math.pi / 3)
|
||||
assert.number_close(vector2.length(v), vector2.length(rotated))
|
||||
end)
|
||||
end)
|
||||
|
||||
it("in_area()", function()
|
||||
assert.is_true(vector2.in_area(vector2.zero(), vector2.new(-10, -10), vector2.new(10, 10)))
|
||||
assert.is_true(vector2.in_area(vector2.new(-2, 5), vector2.new(-10, -10), vector2.new(10, 10)))
|
||||
assert.is_true(vector2.in_area(vector2.new(-10, -10), vector2.new(-10, -10), vector2.new(10, 10)))
|
||||
assert.is_false(vector2.in_area(vector2.new(-11, -10), vector2.new(-10, -10), vector2.new(10, 10)))
|
||||
end)
|
||||
end)
|
||||
@@ -0,0 +1,275 @@
|
||||
--[[
|
||||
2D Vector helpers
|
||||
Note: The vector2.*-functions must be able to accept old vectors that had no metatables
|
||||
]]
|
||||
|
||||
-- localize functions
|
||||
local setmetatable = setmetatable
|
||||
local math = math
|
||||
|
||||
vector2 = {}
|
||||
|
||||
local metatable = {}
|
||||
vector2.metatable = metatable
|
||||
|
||||
local xy = {"x", "y"}
|
||||
|
||||
-- only called when rawget(v, key) returns nil
|
||||
function metatable.__index(v, key)
|
||||
return rawget(v, xy[key]) or vector2[key]
|
||||
end
|
||||
|
||||
-- only called when rawget(v, key) returns nil
|
||||
function metatable.__newindex(v, key, value)
|
||||
rawset(v, xy[key] or key, value)
|
||||
end
|
||||
|
||||
-- constructors
|
||||
|
||||
local function fast_new(x, y)
|
||||
return setmetatable({x = x, y = y}, metatable)
|
||||
end
|
||||
|
||||
function vector2.new(x, y)
|
||||
assert(x and y, "Invalid arguments for vector2.new()")
|
||||
return fast_new(x, y)
|
||||
end
|
||||
|
||||
function vector2.zero()
|
||||
return fast_new(0, 0)
|
||||
end
|
||||
|
||||
function vector2.copy(v)
|
||||
assert(v.x and v.y, "Invalid vector passed to vector2.copy()")
|
||||
return fast_new(v.x, v.y)
|
||||
end
|
||||
|
||||
function vector2.from_angle(angle)
|
||||
assert(angle, "Invalid argument for vector2.from_angle()")
|
||||
return fast_new(math.cos(angle), math.sin(angle))
|
||||
end
|
||||
|
||||
function vector2.from_string(s, init)
|
||||
local x, y, np = string.match(s, "^%s*%(%s*([^%s,]+)%s*[,%s]%s*([^%s,]+)%s*,?%s*%)()", init)
|
||||
x = tonumber(x)
|
||||
y = tonumber(y)
|
||||
if not (x and y) then
|
||||
return
|
||||
end
|
||||
return fast_new(x, y), np
|
||||
end
|
||||
|
||||
function vector2.to_string(v)
|
||||
return string.format("(%g, %g)", v.x, v.y)
|
||||
end
|
||||
metatable.__tostring = vector2.to_string
|
||||
|
||||
function vector2.equals(a, b)
|
||||
return a.x == b.x and a.y == b.y
|
||||
end
|
||||
metatable.__eq = vector2.equals
|
||||
|
||||
-- unary operations
|
||||
|
||||
function vector2.length(v)
|
||||
return math.sqrt(v.x * v.x + v.y * v.y)
|
||||
end
|
||||
|
||||
function vector2.to_angle(v)
|
||||
return math.atan2(v.y, v.x)
|
||||
end
|
||||
|
||||
function vector2.normalize(v)
|
||||
local len = vector2.length(v)
|
||||
if len == 0 then
|
||||
return fast_new(0, 0)
|
||||
else
|
||||
return vector2.divide(v, len)
|
||||
end
|
||||
end
|
||||
|
||||
function vector2.floor(v)
|
||||
return vector2.apply(v, math.floor)
|
||||
end
|
||||
|
||||
function vector2.round(v)
|
||||
return vector2.apply(v, math.round)
|
||||
end
|
||||
|
||||
function vector2.ceil(v)
|
||||
return vector2.apply(v, math.ceil)
|
||||
end
|
||||
|
||||
function vector2.sign(v, tolerance)
|
||||
return vector2.apply(v, math.sign, tolerance)
|
||||
end
|
||||
|
||||
function vector2.abs(v)
|
||||
return vector2.apply(v, math.abs)
|
||||
end
|
||||
|
||||
function vector2.apply(v, func, ...)
|
||||
return fast_new(
|
||||
func(v.x, ...),
|
||||
func(v.y, ...)
|
||||
)
|
||||
end
|
||||
|
||||
function vector2.combine(a, b, func)
|
||||
return fast_new(
|
||||
func(a.x, b.x),
|
||||
func(a.y, b.y)
|
||||
)
|
||||
end
|
||||
|
||||
function vector2.distance(a, b)
|
||||
local x = a.x - b.x
|
||||
local y = a.y - b.y
|
||||
return math.sqrt(x * x + y * y)
|
||||
end
|
||||
|
||||
function vector2.direction(pos1, pos2)
|
||||
return vector2.subtract(pos2, pos1):normalize()
|
||||
end
|
||||
|
||||
function vector2.angle(a, b)
|
||||
local dotp = vector2.dot(a, b)
|
||||
local crossplen = math.abs(a.x * b.y - a.y * b.x)
|
||||
return math.atan2(crossplen, dotp)
|
||||
end
|
||||
|
||||
function vector2.dot(a, b)
|
||||
return a.x * b.x + a.y * b.y
|
||||
end
|
||||
|
||||
function vector2.rotate(v, angle)
|
||||
local cosangle = math.cos(angle)
|
||||
local sinangle = math.sin(angle)
|
||||
return fast_new(
|
||||
v.x * cosangle - v.y * sinangle,
|
||||
v.x * sinangle + v.y * cosangle
|
||||
)
|
||||
end
|
||||
|
||||
function metatable.__unm(v)
|
||||
return fast_new(-v.x, -v.y)
|
||||
end
|
||||
|
||||
-- add, sub, mul, div operations
|
||||
|
||||
function vector2.add(a, b)
|
||||
if type(b) == "table" then
|
||||
return fast_new(
|
||||
a.x + b.x,
|
||||
a.y + b.y
|
||||
)
|
||||
else
|
||||
return fast_new(
|
||||
a.x + b,
|
||||
a.y + b
|
||||
)
|
||||
end
|
||||
end
|
||||
function metatable.__add(a, b)
|
||||
return fast_new(
|
||||
a.x + b.x,
|
||||
a.y + b.y
|
||||
)
|
||||
end
|
||||
|
||||
function vector2.subtract(a, b)
|
||||
if type(b) == "table" then
|
||||
return fast_new(
|
||||
a.x - b.x,
|
||||
a.y - b.y
|
||||
)
|
||||
else
|
||||
return fast_new(
|
||||
a.x - b,
|
||||
a.y - b
|
||||
)
|
||||
end
|
||||
end
|
||||
function metatable.__sub(a, b)
|
||||
return fast_new(
|
||||
a.x - b.x,
|
||||
a.y - b.y
|
||||
)
|
||||
end
|
||||
|
||||
function vector2.multiply(a, b)
|
||||
return fast_new(
|
||||
a.x * b,
|
||||
a.y * b
|
||||
)
|
||||
end
|
||||
function metatable.__mul(a, b)
|
||||
if type(a) == "table" then
|
||||
return fast_new(
|
||||
a.x * b,
|
||||
a.y * b
|
||||
)
|
||||
else
|
||||
return fast_new(
|
||||
a * b.x,
|
||||
a * b.y
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function vector2.divide(a, b)
|
||||
return fast_new(
|
||||
a.x / b,
|
||||
a.y / b
|
||||
)
|
||||
end
|
||||
-- vector÷vector makes no sense
|
||||
metatable.__div = vector2.divide
|
||||
|
||||
-- misc stuff
|
||||
|
||||
function vector2.offset(v, x, y)
|
||||
return fast_new(
|
||||
v.x + x,
|
||||
v.y + y
|
||||
)
|
||||
end
|
||||
|
||||
function vector2.sort(a, b)
|
||||
return fast_new(math.min(a.x, b.x), math.min(a.y, b.y)),
|
||||
fast_new(math.max(a.x, b.x), math.max(a.y, b.y))
|
||||
end
|
||||
|
||||
function vector2.check(v)
|
||||
return getmetatable(v) == metatable
|
||||
end
|
||||
|
||||
function vector2.in_area(pos, min, max)
|
||||
return (pos.x >= min.x) and (pos.x <= max.x) and
|
||||
(pos.y >= min.y) and (pos.y <= max.y)
|
||||
end
|
||||
|
||||
function vector2.random_direction()
|
||||
-- Generate a random direction of unit length
|
||||
local angle = math.random() * 2 * math.pi
|
||||
return fast_new(math.cos(angle), math.sin(angle))
|
||||
end
|
||||
|
||||
if rawget(_G, "core") and core.set_read_vector2 and core.set_push_vector2 then
|
||||
local function read_vector2(v)
|
||||
return v.x, v.y
|
||||
end
|
||||
core.set_read_vector2(read_vector2)
|
||||
core.set_read_vector2 = nil
|
||||
|
||||
if rawget(_G, "jit") then
|
||||
-- This is necessary to prevent trace aborts.
|
||||
local function push_vector2(x, y)
|
||||
return (fast_new(x, y))
|
||||
end
|
||||
core.set_push_vector2(push_vector2)
|
||||
else
|
||||
core.set_push_vector2(fast_new)
|
||||
end
|
||||
core.set_push_vector2 = nil
|
||||
end
|
||||
@@ -44,6 +44,7 @@ local asyncpath = scriptdir .. "async" .. DIR_DELIM
|
||||
|
||||
dofile(commonpath .. "math.lua")
|
||||
dofile(commonpath .. "vector.lua")
|
||||
dofile(commonpath .. "vector2.lua")
|
||||
dofile(commonpath .. "strict.lua")
|
||||
dofile(commonpath .. "serialize.lua")
|
||||
dofile(commonpath .. "misc_helpers.lua")
|
||||
|
||||
+226
-114
@@ -3995,6 +3995,152 @@ or [Wikipedia](https://en.wikipedia.org/wiki/Cartesian_coordinate_system#Orienta
|
||||
for a more detailed and pictorial explanation of these terms.
|
||||
|
||||
|
||||
Vectors
|
||||
=======
|
||||
|
||||
Luanti provides two vector classes for working with coordinates and mathematical operations:
|
||||
|
||||
* **Spatial Vectors** (`vector.*`) - 3-dimensional vectors for 3D positions, directions, and spatial operations
|
||||
* **2D Vectors** (`vector2.*`) - 2-dimensional vectors for 2D positions, screen coordinates, and 2D operations
|
||||
|
||||
Both vector types share many common properties and operations, which are described in the following sections.
|
||||
|
||||
Common to all vector types
|
||||
---------------------------
|
||||
|
||||
### Special properties
|
||||
|
||||
Vectors can be indexed with numbers and allow method and operator syntax.
|
||||
|
||||
All these forms of addressing a vector `v` are valid:
|
||||
|
||||
* For 3D vectors: `v[1]`, `v[3]`, `v.x`, `v[1] = 42`, `v.y = 13`
|
||||
* For 2D vectors: `v[1]`, `v[2]`, `v.x`, `v[1] = 42`, `v.y = 13`
|
||||
|
||||
Note: Prefer letter over number indexing for performance and compatibility reasons.
|
||||
|
||||
Where `v` is a vector and `foo` stands for any function name, `v:foo(...)` does
|
||||
the same as `vector.foo(v, ...)` (or `vector2.foo(v, ...)` for 2D vectors).
|
||||
|
||||
`tostring` is defined for vectors, see `vector.to_string` and `vector2.to_string`.
|
||||
|
||||
The metatable that is used for vectors can be accessed via `vector.metatable` or `vector2.metatable`.
|
||||
Do not modify it!
|
||||
|
||||
All `vector.*` and `vector2.*` functions allow vectors (e.g., `{x = X, y = Y, z = Z}`) without metatables.
|
||||
Returned vectors always have a metatable set.
|
||||
|
||||
Note: Vectors are *not* used for simple numeric arrays of the form `{num, num, num}` or `{num, num}`.
|
||||
Use proper vector tables with named fields (`x`, `y`, `z`) instead.
|
||||
|
||||
### Operators
|
||||
|
||||
Operators can be used if all of the involved vectors have metatables:
|
||||
|
||||
* `v1 == v2`:
|
||||
* Returns whether `v1` and `v2` are identical.
|
||||
* `-v`:
|
||||
* Returns the additive inverse of v.
|
||||
* `v1 + v2`:
|
||||
* Returns the sum of both vectors.
|
||||
* Note: `+` cannot be used together with scalars.
|
||||
* `v1 - v2`:
|
||||
* Returns the difference of `v1` subtracted by `v2`.
|
||||
* Note: `-` cannot be used together with scalars.
|
||||
* `v * s` or `s * v`:
|
||||
* Returns `v` scaled by `s`.
|
||||
* `v / s`:
|
||||
* Returns `v` scaled by `1 / s`.
|
||||
|
||||
### Common functions
|
||||
|
||||
The following functions are available for both `vector` and `vector2` types with the same signature and behavior.
|
||||
Replace `vector` with `vector2` for 2D vectors (e.g., `vector2.add(v, x)`).
|
||||
|
||||
For the following functions,
|
||||
`v`, `v1`, `v2` are vectors (either 3D or 2D depending on context),
|
||||
`p1`, `p2` are position vectors,
|
||||
`s` is a scalar (a number).
|
||||
|
||||
* `vector.copy(v)`:
|
||||
* Returns a copy of the vector `v`.
|
||||
* `vector.zero()`:
|
||||
* Returns a new zero vector.
|
||||
* For 3D: `(0, 0, 0)`. For 2D: `(0, 0)`.
|
||||
* `vector.random_direction()`:
|
||||
* Returns a new vector of length 1, pointing in a direction chosen uniformly at random.
|
||||
* `vector.from_string(s[, init])`:
|
||||
* Returns `v, np`, where `v` is a vector read from the given string `s` and
|
||||
`np` is the next position in the string after the vector.
|
||||
* Returns `nil` on failure.
|
||||
* `s`: Has to begin with a substring of the form `"(x, y, z)"` (for 3D) or `"(x, y)"` (for 2D).
|
||||
Additional spaces, omitting commas and adding an additional comma to the end is allowed.
|
||||
* `init`: If given starts looking for the vector at this string index.
|
||||
* `vector.to_string(v)`:
|
||||
* Returns a human-readable string of the form `"(x, y, z)"` (for 3D) or `"(x, y)"` (for 2D).
|
||||
* `tostring(v)` does the same.
|
||||
* Note: This function loses precision. For exact precision, use `core.serialize()` instead.
|
||||
* Note: Precision may increase in future versions.
|
||||
* `vector.direction(p1, p2)`:
|
||||
* Returns a vector of length 1 with direction `p1` to `p2`.
|
||||
* If `p1` and `p2` are identical, returns a zero vector.
|
||||
* `vector.distance(p1, p2)`:
|
||||
* Returns zero or a positive number, the distance between `p1` and `p2`.
|
||||
* `vector.length(v)`:
|
||||
* Returns zero or a positive number, the length of vector `v`.
|
||||
* `vector.normalize(v)`:
|
||||
* Returns a vector of length 1 with direction of vector `v`.
|
||||
* If `v` has zero length, returns a zero vector.
|
||||
* `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.sign(v, tolerance)`:
|
||||
* Returns a vector where `math.sign` was called for each component.
|
||||
* See [Helper functions](#helper-functions) for details on `math.sign`.
|
||||
* `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.
|
||||
* `vector.equals(v1, v2)`:
|
||||
* Returns a boolean, `true` if the vectors are identical.
|
||||
* `vector.dot(v1, v2)`:
|
||||
* Returns the dot product of `v1` and `v2`.
|
||||
* `vector.check(v)`:
|
||||
* Returns a boolean value indicating whether `v` is a real vector, e.g. created
|
||||
by a `vector.*` or `vector2.*` function.
|
||||
* Returns `false` for anything else, including tables like `{x=3, y=1, z=4}` or `{x=3, y=1}`.
|
||||
* `vector.in_area(pos, min, max)`:
|
||||
* Returns a boolean value indicating if `pos` is inside area formed by `min` and `max`.
|
||||
* `min` and `max` are inclusive.
|
||||
* If `min` is bigger than `max` on some axis, function always returns false.
|
||||
* You can use `vector.sort` (or `vector2.sort` for 2D) 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:
|
||||
|
||||
* `vector.add(v, x)`:
|
||||
* Returns a vector.
|
||||
* If `x` is a vector: Returns the sum of `v` and `x`.
|
||||
* If `x` is a number: Adds `x` to each component of `v`.
|
||||
* `vector.subtract(v, x)`:
|
||||
* Returns a vector.
|
||||
* If `x` is a vector: Returns the difference of `v` subtracted by `x`.
|
||||
* If `x` is a number: Subtracts `x` from each component of `v`.
|
||||
* `vector.multiply(v, s)`:
|
||||
* Returns a scaled vector.
|
||||
* For `vector` only, deprecated behavior: If `s` is a vector, returns the Schur product.
|
||||
* `vector.divide(v, s)`:
|
||||
* Returns a scaled vector.
|
||||
* For `vector` only, deprecated behavior: If `s` is a vector, returns the Schur quotient.
|
||||
|
||||
|
||||
Spatial Vectors
|
||||
===============
|
||||
|
||||
@@ -4008,11 +4154,6 @@ Spatial vectors are used for various things, including, but not limited to:
|
||||
* Euler angles (pitch/yaw/roll in radians) (Spatial vectors have no real semantic
|
||||
meaning here. Therefore, most vector operations make no sense in this use case.)
|
||||
|
||||
Note that they are *not* used for:
|
||||
|
||||
* n-dimensional vectors where n is not 3 (ie. n=2)
|
||||
* arrays of the form `{num, num, num}`
|
||||
|
||||
The API documentation may refer to spatial vectors, as produced by `vector.new`,
|
||||
by any of the following notations:
|
||||
|
||||
@@ -4043,27 +4184,18 @@ stated otherwise. Mods should adapt this for convenience reasons.
|
||||
Special properties of the class
|
||||
-------------------------------
|
||||
|
||||
Vectors can be indexed with numbers and allow method and operator syntax.
|
||||
For special properties common to all vector types (indexing, method syntax, operators, etc.),
|
||||
see [Common to all vector types](#common-to-all-vector-types).
|
||||
|
||||
All these forms of addressing a vector `v` are valid:
|
||||
`v[1]`, `v[3]`, `v.x`, `v[1] = 42`, `v.y = 13`
|
||||
Note: Prefer letter over number indexing for performance and compatibility reasons.
|
||||
Functions
|
||||
---------
|
||||
|
||||
Where `v` is a vector and `foo` stands for any function name, `v:foo(...)` does
|
||||
the same as `vector.foo(v, ...)`, apart from deprecated functionality.
|
||||
For common functions available to both `vector` and `vector2`,
|
||||
see [Common functions](#common-functions).
|
||||
|
||||
`tostring` is defined for vectors, see `vector.to_string`.
|
||||
The following functions are specific to `vector` (3D vectors).
|
||||
|
||||
The metatable that is used for vectors can be accessed via `vector.metatable`.
|
||||
Do not modify it!
|
||||
|
||||
All `vector.*` functions allow vectors `{x = X, y = Y, z = Z}` without metatables.
|
||||
Returned vectors always have a metatable set.
|
||||
|
||||
Common functions and methods
|
||||
----------------------------
|
||||
|
||||
For the following functions (and subchapters),
|
||||
For the following functions,
|
||||
`v`, `v1`, `v2` are vectors,
|
||||
`p1`, `p2` are position vectors,
|
||||
`s` is a scalar (a number),
|
||||
@@ -4073,114 +4205,23 @@ vectors are written like this: `(x, y, z)`:
|
||||
* Returns a new vector `(a, b, c)`.
|
||||
* Deprecated: `vector.new()` does the same as `vector.zero()` and
|
||||
`vector.new(v)` does the same as `vector.copy(v)`
|
||||
* `vector.zero()`:
|
||||
* Returns a new vector `(0, 0, 0)`.
|
||||
* `vector.random_direction()`:
|
||||
* Returns a new vector of length 1, pointing into a direction chosen uniformly at random.
|
||||
* `vector.copy(v)`:
|
||||
* Returns a copy of the vector `v`.
|
||||
* `vector.from_string(s[, init])`:
|
||||
* Returns `v, np`, where `v` is a vector read from the given string `s` and
|
||||
`np` is the next position in the string after the vector.
|
||||
* Returns `nil` on failure.
|
||||
* `s`: Has to begin with a substring of the form `"(x, y, z)"`. Additional
|
||||
spaces, leaving away commas and adding an additional comma to the end
|
||||
is allowed.
|
||||
* `init`: If given starts looking for the vector at this string index.
|
||||
* `vector.to_string(v)`:
|
||||
* Returns a string of the form `"(x, y, z)"`.
|
||||
* `tostring(v)` does the same.
|
||||
* `vector.direction(p1, p2)`:
|
||||
* Returns a vector of length 1 with direction `p1` to `p2`.
|
||||
* If `p1` and `p2` are identical, returns `(0, 0, 0)`.
|
||||
* `vector.distance(p1, p2)`:
|
||||
* Returns zero or a positive number, the distance between `p1` and `p2`.
|
||||
* `vector.length(v)`:
|
||||
* Returns zero or a positive number, the length of vector `v`.
|
||||
* `vector.normalize(v)`:
|
||||
* Returns a vector of length 1 with direction of vector `v`.
|
||||
* 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.sign(v, tolerance)`:
|
||||
* Returns a vector where `math.sign` was called for each component.
|
||||
* See [Helper functions](#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
|
||||
* `vector.equals(v1, v2)`:
|
||||
* Returns a boolean, `true` if the vectors are identical.
|
||||
* `vector.sort(v1, v2)`:
|
||||
* Returns in order minp, maxp vectors of the cuboid defined by `v1`, `v2`.
|
||||
* `vector.angle(v1, v2)`:
|
||||
* Returns the angle between `v1` and `v2` in radians.
|
||||
* `vector.dot(v1, v2)`:
|
||||
* Returns the dot product of `v1` and `v2`.
|
||||
* `vector.cross(v1, v2)`:
|
||||
* Returns the cross product of `v1` and `v2`.
|
||||
* `vector.offset(v, x, y, z)`:
|
||||
* Returns the sum of the vectors `v` and `(x, y, z)`.
|
||||
* `vector.check(v)`:
|
||||
* Returns a boolean value indicating whether `v` is a real vector, eg. created
|
||||
by a `vector.*` function.
|
||||
* Returns `false` for anything else, including tables like `{x=3,y=1,z=4}`.
|
||||
* `vector.in_area(pos, min, max)`:
|
||||
* Returns a boolean value indicating if `pos` is inside area formed by `min` and `max`.
|
||||
* `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:
|
||||
|
||||
* `vector.add(v, x)`:
|
||||
* Returns a vector.
|
||||
* If `x` is a vector: Returns the sum of `v` and `x`.
|
||||
* If `x` is a number: Adds `x` to each component of `v`.
|
||||
* `vector.subtract(v, x)`:
|
||||
* Returns a vector.
|
||||
* If `x` is a vector: Returns the difference of `v` subtracted by `x`.
|
||||
* If `x` is a number: Subtracts `x` from each component of `v`.
|
||||
* `vector.multiply(v, s)`:
|
||||
* Returns a scaled vector.
|
||||
* Deprecated: If `s` is a vector: Returns the Schur product.
|
||||
* `vector.divide(v, s)`:
|
||||
* Returns a scaled vector.
|
||||
* Deprecated: If `s` is a vector: Returns the Schur quotient.
|
||||
|
||||
Operators
|
||||
---------
|
||||
|
||||
Operators can be used if all of the involved vectors have metatables:
|
||||
|
||||
* `v1 == v2`:
|
||||
* Returns whether `v1` and `v2` are identical.
|
||||
* `-v`:
|
||||
* Returns the additive inverse of v.
|
||||
* `v1 + v2`:
|
||||
* Returns the sum of both vectors.
|
||||
* Note: `+` cannot be used together with scalars.
|
||||
* `v1 - v2`:
|
||||
* Returns the difference of `v1` subtracted by `v2`.
|
||||
* Note: `-` cannot be used together with scalars.
|
||||
* `v * s` or `s * v`:
|
||||
* Returns `v` scaled by `s`.
|
||||
* `v / s`:
|
||||
* Returns `v` scaled by `1 / s`.
|
||||
For vector operators (`+`, `-`, `*`, `/`, `==`, unary `-`), see [Common to all vector types](#common-to-all-vector-types).
|
||||
|
||||
Rotation-related functions
|
||||
--------------------------
|
||||
@@ -4219,6 +4260,77 @@ For example:
|
||||
|
||||
|
||||
|
||||
2D Vectors
|
||||
==========
|
||||
|
||||
Luanti stores 2-dimensional vectors in Lua as tables of 2 coordinates,
|
||||
and has a class to represent them (`vector2.*`).
|
||||
|
||||
The API provides `vector2.new` to create vectors:
|
||||
|
||||
* `vector2.new(x, y)`
|
||||
* `{x=num, y=num}` (Even here you are still supposed to use `vector2.new`.)
|
||||
|
||||
Compatibility notes
|
||||
-------------------
|
||||
|
||||
Vectors should be created using `vector2.new(x, y)` to ensure they have the
|
||||
proper metatable. This enables:
|
||||
|
||||
* Method call syntax (e.g., `v:length()` instead of `vector2.length(v)`)
|
||||
* Operator overloading (e.g., `v1 + v2` instead of `vector2.add(v1, v2)`)
|
||||
* Type checking with `vector2.check()`
|
||||
|
||||
Special properties of the class
|
||||
-------------------------------
|
||||
|
||||
For special properties common to all vector types (indexing, method syntax, operators, etc.),
|
||||
see [Common to all vector types](#common-to-all-vector-types).
|
||||
|
||||
Functions
|
||||
---------
|
||||
|
||||
For common functions available to both `vector` and `vector2`,
|
||||
see [Common functions](#common-functions).
|
||||
|
||||
The following functions are specific to `vector2` (2D vectors).
|
||||
|
||||
For the following functions,
|
||||
`v`, `v1`, `v2` are vectors,
|
||||
`p1`, `p2` are position vectors,
|
||||
`s` is a scalar (a number),
|
||||
vectors are written like this: `(x, y)`:
|
||||
|
||||
* `vector2.new(x, y)`:
|
||||
* Returns a new vector `(x, y)`.
|
||||
* `vector2.from_angle(angle)`:
|
||||
* Returns a new unit vector from an angle.
|
||||
* `angle` is the angle in radians from the positive x-axis (counterclockwise).
|
||||
* Example: `vector2.from_angle(math.pi / 2)` returns a vector pointing up `(0, 1)`.
|
||||
* `vector2.to_angle(v)`:
|
||||
* Returns the angle of the vector in radians.
|
||||
* `angle` is the angle from the positive x-axis (counterclockwise), in the range `(-pi, pi]`.
|
||||
* The edge case of `(0, 0)` returns `0`.
|
||||
* Example: `vector2.to_angle(vector2.new(0, 1))` returns `math.pi / 2`.
|
||||
* `vector2.sort(v1, v2)`:
|
||||
* Returns in order minp, maxp vectors of the rectangle defined by `v1`, `v2`.
|
||||
* `vector2.angle(v1, v2)`:
|
||||
* Returns the angle between `v1` and `v2` in radians.
|
||||
* This is always a positive value (unsigned angle).
|
||||
* `vector2.rotate(v, angle)`:
|
||||
* Returns a new vector rotated counterclockwise by `angle` radians around the origin.
|
||||
* The length of the vector is preserved.
|
||||
* Example: `vector2.rotate(vector2.new(1, 0), math.pi / 2)` returns `(0, 1)`.
|
||||
* `vector2.offset(v, x, y)`:
|
||||
* Returns the sum of the vectors `v` and `(x, y)`.
|
||||
|
||||
Operators
|
||||
---------
|
||||
|
||||
For vector operators (`+`, `-`, `*`, `/`, `==`, unary `-`), see [Common to all vector types](#common-to-all-vector-types).
|
||||
|
||||
|
||||
|
||||
Helper functions
|
||||
================
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ read_globals = {
|
||||
"dump", "dump2",
|
||||
"fgettext", "fgettext_ne",
|
||||
"vector",
|
||||
"vector2",
|
||||
"VoxelArea",
|
||||
"VoxelManip",
|
||||
"profiler",
|
||||
|
||||
@@ -201,6 +201,7 @@ dofile(modpath .. "/inventory.lua")
|
||||
dofile(modpath .. "/load_time.lua")
|
||||
dofile(modpath .. "/on_shutdown.lua")
|
||||
dofile(modpath .. "/color.lua")
|
||||
dofile(modpath .. "/vector2.lua")
|
||||
|
||||
--------------
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
--
|
||||
-- Vector2 engine API push/read test
|
||||
--
|
||||
-- This test verifies that the engine correctly pushes and reads 2D vectors
|
||||
-- to and from Lua. It uses the spritediv property on player object properties,
|
||||
-- which is a 2D vector in the engine (v2s16).
|
||||
-- The test ensures that metatables are correctly set by vector2.check().
|
||||
--
|
||||
local function test_vector2_push_read(player)
|
||||
-- Get original properties to restore later
|
||||
local old_props = player:get_properties()
|
||||
|
||||
-- Set a vector2 value via engine API (spritediv is a v2s16 in the engine)
|
||||
local test_vector = vector2.new(5, 8)
|
||||
player:set_properties({spritediv = test_vector})
|
||||
|
||||
-- Read back the value from engine
|
||||
local props = player:get_properties()
|
||||
local retrieved_vector = props.spritediv
|
||||
|
||||
-- Verify the engine correctly pushed a vector2 with proper metatable
|
||||
assert(vector2.check(retrieved_vector), "Retrieved spritediv is not a valid vector2")
|
||||
|
||||
-- Verify the values are correct
|
||||
assert(retrieved_vector.x == 5, "spritediv.x should be 5")
|
||||
assert(retrieved_vector.y == 8, "spritediv.y should be 8")
|
||||
|
||||
-- Test with a table (should be converted by engine)
|
||||
player:set_properties({spritediv = {x = 3, y = 7}})
|
||||
props = player:get_properties()
|
||||
retrieved_vector = props.spritediv
|
||||
|
||||
-- Verify the engine converted the table to a proper vector2
|
||||
assert(vector2.check(retrieved_vector), "Retrieved spritediv from table is not a valid vector2")
|
||||
assert(retrieved_vector.x == 3, "spritediv.x should be 3")
|
||||
assert(retrieved_vector.y == 7, "spritediv.y should be 7")
|
||||
|
||||
-- Restore original properties
|
||||
player:set_properties({spritediv = old_props.spritediv})
|
||||
end
|
||||
|
||||
unittests.register("test_vector2_push_read", test_vector2_push_read, {player=true})
|
||||
@@ -71,6 +71,18 @@ static void read_v3_aux(lua_State *L, int index)
|
||||
lua_call(L, 1, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper which calls CUSTOM_RIDX_READ_VECTOR2 with the argument at the given index
|
||||
*/
|
||||
static void read_v2_aux(lua_State *L, int index)
|
||||
{
|
||||
CHECK_POS_TAB(index);
|
||||
lua_pushvalue(L, index);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR2);
|
||||
lua_insert(L, -2);
|
||||
lua_call(L, 1, 2);
|
||||
}
|
||||
|
||||
// Retrieve an integer vector where all components are optional
|
||||
template<class T>
|
||||
static bool getv3intfield(lua_State *L, int index,
|
||||
@@ -98,11 +110,10 @@ void push_v3f(lua_State *L, v3f p)
|
||||
|
||||
void push_v2f(lua_State *L, v2f p)
|
||||
{
|
||||
lua_createtable(L, 0, 2);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_VECTOR2);
|
||||
lua_pushnumber(L, p.X);
|
||||
lua_setfield(L, -2, "x");
|
||||
lua_pushnumber(L, p.Y);
|
||||
lua_setfield(L, -2, "y");
|
||||
lua_call(L, 2, 1);
|
||||
}
|
||||
|
||||
v2s16 read_v2s16(lua_State *L, int index)
|
||||
@@ -117,20 +128,18 @@ void push_v2s16(lua_State *L, v2s16 p)
|
||||
|
||||
void push_v2s32(lua_State *L, v2s32 p)
|
||||
{
|
||||
lua_createtable(L, 0, 2);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_VECTOR2);
|
||||
lua_pushinteger(L, p.X);
|
||||
lua_setfield(L, -2, "x");
|
||||
lua_pushinteger(L, p.Y);
|
||||
lua_setfield(L, -2, "y");
|
||||
lua_call(L, 2, 1);
|
||||
}
|
||||
|
||||
void push_v2u32(lua_State *L, v2u32 p)
|
||||
{
|
||||
lua_createtable(L, 0, 2);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_VECTOR2);
|
||||
lua_pushinteger(L, p.X);
|
||||
lua_setfield(L, -2, "x");
|
||||
lua_pushinteger(L, p.Y);
|
||||
lua_setfield(L, -2, "y");
|
||||
lua_call(L, 2, 1);
|
||||
}
|
||||
|
||||
v2s32 read_v2s32(lua_State *L, int index)
|
||||
@@ -140,34 +149,26 @@ v2s32 read_v2s32(lua_State *L, int index)
|
||||
|
||||
v2f read_v2f(lua_State *L, int index)
|
||||
{
|
||||
v2f p;
|
||||
CHECK_POS_TAB(index);
|
||||
lua_getfield(L, index, "x");
|
||||
CHECK_POS_COORD2(-1, "x");
|
||||
p.X = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "y");
|
||||
read_v2_aux(L, index);
|
||||
CHECK_POS_COORD2(-2, "x");
|
||||
CHECK_POS_COORD2(-1, "y");
|
||||
p.Y = lua_tonumber(L, -1);
|
||||
lua_pop(L, 1);
|
||||
return p;
|
||||
float x = lua_tonumber(L, -2);
|
||||
float y = lua_tonumber(L, -1);
|
||||
lua_pop(L, 2);
|
||||
return v2f(x, y);
|
||||
}
|
||||
|
||||
v2f check_v2f(lua_State *L, int index)
|
||||
{
|
||||
v2f p;
|
||||
CHECK_POS_TAB(index);
|
||||
lua_getfield(L, index, "x");
|
||||
CHECK_POS_COORD(-1, "x");
|
||||
p.X = lua_tonumber(L, -1);
|
||||
CHECK_FLOAT(p.X, "x");
|
||||
lua_pop(L, 1);
|
||||
lua_getfield(L, index, "y");
|
||||
read_v2_aux(L, index);
|
||||
CHECK_POS_COORD(-2, "x");
|
||||
CHECK_POS_COORD(-1, "y");
|
||||
p.Y = lua_tonumber(L, -1);
|
||||
CHECK_FLOAT(p.Y, "y");
|
||||
lua_pop(L, 1);
|
||||
return p;
|
||||
float x = lua_tonumber(L, -2);
|
||||
float y = lua_tonumber(L, -1);
|
||||
lua_pop(L, 2);
|
||||
CHECK_FLOAT(x, "x");
|
||||
CHECK_FLOAT(y, "y");
|
||||
return v2f(x, y);
|
||||
}
|
||||
|
||||
v3f read_v3f(lua_State *L, int index)
|
||||
|
||||
@@ -49,6 +49,8 @@ enum {
|
||||
// trace them and optimize tables/string better than from the C API.
|
||||
CUSTOM_RIDX_READ_VECTOR,
|
||||
CUSTOM_RIDX_PUSH_VECTOR,
|
||||
CUSTOM_RIDX_READ_VECTOR2,
|
||||
CUSTOM_RIDX_PUSH_VECTOR2,
|
||||
CUSTOM_RIDX_READ_NODE,
|
||||
CUSTOM_RIDX_PUSH_NODE,
|
||||
CUSTOM_RIDX_PUSH_MOVERESULT1,
|
||||
|
||||
@@ -127,6 +127,16 @@ ScriptApiBase::ScriptApiBase(ScriptingType type):
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(m_luastack, -2, "set_push_vector");
|
||||
lua_pushcfunction(m_luastack, [](lua_State *L) -> int {
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_VECTOR2);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(m_luastack, -2, "set_read_vector2");
|
||||
lua_pushcfunction(m_luastack, [](lua_State *L) -> int {
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_PUSH_VECTOR2);
|
||||
return 0;
|
||||
});
|
||||
lua_setfield(m_luastack, -2, "set_push_vector2");
|
||||
lua_pushcfunction(m_luastack, [](lua_State *L) -> int {
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, CUSTOM_RIDX_READ_NODE);
|
||||
return 0;
|
||||
@@ -209,6 +219,8 @@ void ScriptApiBase::checkSetByBuiltin()
|
||||
|
||||
CHECK(CUSTOM_RIDX_READ_VECTOR, "read_vector");
|
||||
CHECK(CUSTOM_RIDX_PUSH_VECTOR, "push_vector");
|
||||
CHECK(CUSTOM_RIDX_READ_VECTOR2, "read_vector2");
|
||||
CHECK(CUSTOM_RIDX_PUSH_VECTOR2, "push_vector2");
|
||||
|
||||
if (getType() == ScriptingType::Server ||
|
||||
(getType() == ScriptingType::Async && m_gamedef) ||
|
||||
|
||||
@@ -103,6 +103,12 @@ void TestScriptApi::testVectorMetatable(MyScriptApi *script)
|
||||
return lua_toboolean(L, -1);
|
||||
};
|
||||
|
||||
const auto &call_vector2_check = [&] () -> bool {
|
||||
lua_setglobal(L, "tmp");
|
||||
run(L, "return vector2.check(tmp)", 1);
|
||||
return lua_toboolean(L, -1);
|
||||
};
|
||||
|
||||
push_v3s16(L, {1, 2, 3});
|
||||
UASSERT(call_vector_check());
|
||||
|
||||
@@ -115,6 +121,13 @@ void TestScriptApi::testVectorMetatable(MyScriptApi *script)
|
||||
|
||||
push_v2f(L, {0, 0});
|
||||
UASSERT(!call_vector_check());
|
||||
|
||||
// but they must have the vector2 metatable
|
||||
push_v2s32(L, {0, 0});
|
||||
UASSERT(call_vector2_check());
|
||||
|
||||
push_v2f(L, {0, 0});
|
||||
UASSERT(call_vector2_check());
|
||||
}
|
||||
|
||||
void TestScriptApi::testVectorRead(MyScriptApi *script)
|
||||
|
||||
Reference in New Issue
Block a user