Add metatables to lua vectors (#11039)

Add backwards-compatible metatable functions for vectors.
This commit is contained in:
DS 2021-06-04 21:22:33 +02:00 committed by GitHub
parent e15cae9fa0
commit 8f085e02a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 570 additions and 179 deletions

@ -7,5 +7,4 @@ dofile(clientpath .. "register.lua")
dofile(commonpath .. "after.lua")
dofile(commonpath .. "chatcommands.lua")
dofile(clientpath .. "chatcommands.lua")
dofile(commonpath .. "vector.lua")
dofile(clientpath .. "death_formspec.lua")

@ -432,21 +432,19 @@ function core.string_to_pos(value)
return nil
end
local p = {}
p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
return p
local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
if x and y and z then
x = tonumber(x)
y = tonumber(y)
z = tonumber(z)
return vector.new(x, y, z)
end
p = {}
p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
if p.x and p.y and p.z then
p.x = tonumber(p.x)
p.y = tonumber(p.y)
p.z = tonumber(p.z)
return p
x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
if x and y and z then
x = tonumber(x)
y = tonumber(y)
z = tonumber(z)
return vector.new(x, y, z)
end
return nil
end

@ -1,4 +1,5 @@
_G.core = {}
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
describe("string", function()
@ -55,8 +56,8 @@ end)
describe("pos", function()
it("from string", function()
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2"))
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)"))
assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("10.0, 5.1, -2"))
assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("( 10.0, 5.1, -2)"))
assert.is_nil(core.string_to_pos("asd, 5, -2)"))
end)

@ -3,6 +3,7 @@ _G.core = {}
_G.setfenv = require 'busted.compatibility'.setfenv
dofile("builtin/common/serialize.lua")
dofile("builtin/common/vector.lua")
describe("serialize", function()
it("works", function()
@ -53,4 +54,16 @@ describe("serialize", function()
assert.is_nil(test_out.func)
assert.equals(test_out.foo, "bar")
end)
it("vectors work", function()
local v = vector.new(1, 2, 3)
assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v})))
assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v)))
-- abuse
v = vector.new(1, 2, 3)
v.a = "bla"
assert.same({x = 1, y = 2, z = 3, a = "bla"},
core.deserialize(core.serialize(v)))
end)
end)

@ -8,10 +8,16 @@ describe("vector", function()
assert.same({x = 1, y = 2, z = 3}, vector.new(1, 2, 3))
assert.same({x = 3, y = 2, z = 1}, vector.new({x = 3, y = 2, z = 1}))
assert.is_true(vector.check(vector.new()))
assert.is_true(vector.check(vector.new(1, 2, 3)))
assert.is_true(vector.check(vector.new({x = 3, y = 2, z = 1})))
local input = vector.new({ x = 3, y = 2, z = 1 })
local output = vector.new(input)
assert.same(input, output)
assert.are_not.equal(input, output)
assert.equal(input, output)
assert.is_false(rawequal(input, output))
assert.equal(input, input:new())
end)
it("throws on invalid input", function()
@ -25,7 +31,89 @@ describe("vector", function()
end)
end)
it("equal()", function()
it("indexes", function()
local some_vector = vector.new(24, 42, 13)
assert.equal(24, some_vector[1])
assert.equal(24, some_vector.x)
assert.equal(42, some_vector[2])
assert.equal(42, some_vector.y)
assert.equal(13, some_vector[3])
assert.equal(13, some_vector.z)
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])
some_vector[3] = 100
assert.equal(100, some_vector.z)
some_vector.z = 103
assert.equal(103, some_vector[3])
end)
it("direction()", function()
local a = vector.new(1, 0, 0)
local b = vector.new(1, 42, 0)
assert.equal(vector.new(0, 1, 0), vector.direction(a, b))
assert.equal(vector.new(0, 1, 0), a:direction(b))
end)
it("distance()", function()
local a = vector.new(1, 0, 0)
local b = vector.new(3, 42, 9)
assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12)
assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12)
assert.equal(0, vector.distance(a, a))
assert.equal(0, b:distance(b))
end)
it("length()", function()
local a = vector.new(0, 0, -23)
assert.equal(0, vector.length(vector.new()))
assert.equal(23, vector.length(a))
assert.equal(23, a:length())
end)
it("normalize()", function()
local a = vector.new(0, 0, -23)
assert.equal(vector.new(0, 0, -1), vector.normalize(a))
assert.equal(vector.new(0, 0, -1), a:normalize())
assert.equal(vector.new(), vector.normalize(vector.new()))
end)
it("floor()", function()
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(0, 0, -1), vector.floor(a))
assert.equal(vector.new(0, 0, -1), a:floor())
end)
it("round()", function()
local a = vector.new(0.1, 0.9, -0.5)
assert.equal(vector.new(0, 1, -1), vector.round(a))
assert.equal(vector.new(0, 1, -1), a:round())
end)
it("apply()", function()
local i = 0
local f = function(x)
i = i + 1
return x + i
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))
assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs))
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))
end)
it("equals()", function()
local function assertE(a, b)
assert.is_true(vector.equals(a, b))
end
@ -35,22 +123,164 @@ describe("vector", function()
assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1))
local a = {x = 2, y = 4, z = -10}
assertE(a, a)
assertNE({x = -1, y = 0, z = 1}, a)
assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3))
assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3)))
assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4))
assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3))
assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3))
end)
it("add()", function()
assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
it("metatable is same", function()
local a = vector.new()
local b = vector.new(1, 2, 3)
assert.equal(true, vector.check(a))
assert.equal(true, vector.check(b))
assert.equal(vector.metatable, getmetatable(a))
assert.equal(vector.metatable, getmetatable(b))
assert.equal(vector.metatable, a.metatable)
end)
it("sort()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(0.5, 232, -2)
local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)}
assert.same(sorted, {vector.sort(a, b)})
assert.same(sorted, {a:sort(b)})
end)
it("angle()", function()
assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0)))
end)
it("dot()", function()
assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
assert.equal(0, vector.new():dot(vector.new(1, 2, 3)))
end)
it("cross()", function()
local a = vector.new(-1, -2, 0)
local b = vector.new(1, 2, 3)
assert.equal(vector.new(-6, 3, 0), vector.cross(a, b))
assert.equal(vector.new(-6, 3, 0), a:cross(b))
end)
it("offset()", function()
assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60))
assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60))
end)
it("is()", function()
local some_table1 = {foo = 13, [42] = 1, "bar", 2}
local some_table2 = {1, 2, 3}
local some_table3 = {x = 1, 2, 3}
local some_table4 = {1, 2, z = 3}
local old = {x = 1, y = 2, z = 3}
local real = vector.new(1, 2, 3)
assert.is_false(vector.check(nil))
assert.is_false(vector.check(1))
assert.is_false(vector.check(true))
assert.is_false(vector.check("foo"))
assert.is_false(vector.check(some_table1))
assert.is_false(vector.check(some_table2))
assert.is_false(vector.check(some_table3))
assert.is_false(vector.check(some_table4))
assert.is_false(vector.check(old))
assert.is_true(vector.check(real))
assert.is_true(real:check())
end)
it("global pairs", function()
local out = {}
local vec = vector.new(10, 20, 30)
for k, v in pairs(vec) do
out[k] = v
end
assert.same({x = 10, y = 20, z = 30}, out)
end)
it("abusing works", function()
local v = vector.new(1, 2, 3)
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 = vector.new(1, 2, 3)
local b = vector.new(1, 4, 3)
local c = vector.new(2, 6, 6)
assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3}))
assert.equal(c, vector.add(a, b))
assert.equal(c, a:add(b))
assert.equal(c, a + b)
assert.equal(c, b + a)
end)
it("subtract()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(2, 4, 3)
local c = vector.new(-1, -2, 0)
assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3}))
assert.equal(c, vector.subtract(a, b))
assert.equal(c, a:subtract(b))
assert.equal(c, a - b)
assert.equal(c, -b + a)
end)
it("multiply()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(2, 4, 3)
local c = vector.new(2, 8, 9)
local s = 2
local d = vector.new(2, 4, 6)
assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3}))
assert.equal(c, vector.multiply(a, b))
assert.equal(d, vector.multiply(a, s))
assert.equal(d, a:multiply(s))
assert.equal(d, a * s)
assert.equal(d, s * a)
assert.equal(-a, -1 * a)
end)
it("divide()", function()
local a = vector.new(1, 2, 3)
local b = vector.new(2, 4, 3)
local c = vector.new(0.5, 0.5, 1)
local s = 2
local d = vector.new(0.5, 1, 1.5)
assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3}))
assert.equal(c, vector.divide(a, b))
assert.equal(d, vector.divide(a, s))
assert.equal(d, a:divide(s))
assert.equal(d, a / s)
assert.equal(d, 1/s * a)
assert.equal(-a, a / -1)
end)
it("to_string()", function()
local v = vector.new(1, 2, 3.14)
assert.same("(1, 2, 3.14)", vector.to_string(v))
assert.same("(1, 2, 3.14)", v:to_string())
assert.same("(1, 2, 3.14)", tostring(v))
end)
it("from_string()", function()

@ -1,15 +1,43 @@
--[[
Vector helpers
Note: The vector.*-functions must be able to accept old vectors that had no metatables
]]
-- localize functions
local setmetatable = setmetatable
vector = {}
local metatable = {}
vector.metatable = metatable
local xyz = {"x", "y", "z"}
-- only called when rawget(v, key) returns nil
function metatable.__index(v, key)
return rawget(v, xyz[key]) or vector[key]
end
-- only called when rawget(v, key) returns nil
function metatable.__newindex(v, key, value)
rawset(v, xyz[key] or key, value)
end
-- constructors
local function fast_new(x, y, z)
return setmetatable({x = x, y = y, z = z}, metatable)
end
function vector.new(a, b, c)
if type(a) == "table" then
assert(a.x and a.y and a.z, "Invalid vector passed to vector.new()")
return {x=a.x, y=a.y, z=a.z}
return fast_new(a.x, a.y, a.z)
elseif a then
assert(b and c, "Invalid arguments for vector.new()")
return {x=a, y=b, z=c}
return fast_new(a, b, c)
end
return {x=0, y=0, z=0}
return fast_new(0, 0, 0)
end
function vector.from_string(s, init)
@ -27,48 +55,49 @@ end
function vector.to_string(v)
return string.format("(%g, %g, %g)", v.x, v.y, v.z)
end
metatable.__tostring = vector.to_string
function vector.equals(a, b)
return a.x == b.x and
a.y == b.y and
a.z == b.z
end
metatable.__eq = vector.equals
-- unary operations
function vector.length(v)
return math.hypot(v.x, math.hypot(v.y, v.z))
end
-- Note: we can not use __len because it is already used for primitive table length
function vector.normalize(v)
local len = vector.length(v)
if len == 0 then
return {x=0, y=0, z=0}
return fast_new(0, 0, 0)
else
return vector.divide(v, len)
end
end
function vector.floor(v)
return {
x = math.floor(v.x),
y = math.floor(v.y),
z = math.floor(v.z)
}
return vector.apply(v, math.floor)
end
function vector.round(v)
return {
x = math.round(v.x),
y = math.round(v.y),
z = math.round(v.z)
}
return fast_new(
math.round(v.x),
math.round(v.y),
math.round(v.z)
)
end
function vector.apply(v, func)
return {
x = func(v.x),
y = func(v.y),
z = func(v.z)
}
return fast_new(
func(v.x),
func(v.y),
func(v.z)
)
end
function vector.distance(a, b)
@ -79,11 +108,7 @@ function vector.distance(a, b)
end
function vector.direction(pos1, pos2)
return vector.normalize({
x = pos2.x - pos1.x,
y = pos2.y - pos1.y,
z = pos2.z - pos1.z
})
return vector.subtract(pos2, pos1):normalize()
end
function vector.angle(a, b)
@ -98,70 +123,137 @@ function vector.dot(a, b)
end
function vector.cross(a, b)
return {
x = a.y * b.z - a.z * b.y,
y = a.z * b.x - a.x * b.z,
z = a.x * b.y - a.y * b.x
}
return fast_new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x
)
end
function metatable.__unm(v)
return fast_new(-v.x, -v.y, -v.z)
end
-- add, sub, mul, div operations
function vector.add(a, b)
if type(b) == "table" then
return {x = a.x + b.x,
y = a.y + b.y,
z = a.z + b.z}
return fast_new(
a.x + b.x,
a.y + b.y,
a.z + b.z
)
else
return {x = a.x + b,
y = a.y + b,
z = a.z + b}
return fast_new(
a.x + b,
a.y + b,
a.z + b
)
end
end
function metatable.__add(a, b)
return fast_new(
a.x + b.x,
a.y + b.y,
a.z + b.z
)
end
function vector.subtract(a, b)
if type(b) == "table" then
return {x = a.x - b.x,
y = a.y - b.y,
z = a.z - b.z}
return fast_new(
a.x - b.x,
a.y - b.y,
a.z - b.z
)
else
return {x = a.x - b,
y = a.y - b,
z = a.z - b}
return fast_new(
a.x - b,
a.y - b,
a.z - b
)
end
end
function metatable.__sub(a, b)
return fast_new(
a.x - b.x,
a.y - b.y,
a.z - b.z
)
end
function vector.multiply(a, b)
if type(b) == "table" then
return {x = a.x * b.x,
y = a.y * b.y,
z = a.z * b.z}
return fast_new(
a.x * b.x,
a.y * b.y,
a.z * b.z
)
else
return {x = a.x * b,
y = a.y * b,
z = a.z * b}
return fast_new(
a.x * b,
a.y * b,
a.z * b
)
end
end
function metatable.__mul(a, b)
if type(a) == "table" then
return fast_new(
a.x * b,
a.y * b,
a.z * b
)
else
return fast_new(
a * b.x,
a * b.y,
a * b.z
)
end
end
function vector.divide(a, b)
if type(b) == "table" then
return {x = a.x / b.x,
y = a.y / b.y,
z = a.z / b.z}
return fast_new(
a.x / b.x,
a.y / b.y,
a.z / b.z
)
else
return {x = a.x / b,
y = a.y / b,
z = a.z / b}
return fast_new(
a.x / b,
a.y / b,
a.z / b
)
end
end
function metatable.__div(a, b)
-- scalar/vector makes no sense
return fast_new(
a.x / b,
a.y / b,
a.z / b
)
end
-- misc stuff
function vector.offset(v, x, y, z)
return {x = v.x + x,
y = v.y + y,
z = v.z + z}
return fast_new(
v.x + x,
v.y + y,
v.z + z
)
end
function vector.sort(a, b)
return {x = math.min(a.x, b.x), y = math.min(a.y, b.y), z = math.min(a.z, b.z)},
{x = math.max(a.x, b.x), y = math.max(a.y, b.y), z = math.max(a.z, b.z)}
return fast_new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z)),
fast_new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
end
function vector.check(v)
return getmetatable(v) == metatable
end
local function sin(x)
@ -229,7 +321,7 @@ end
function vector.dir_to_rotation(forward, up)
forward = vector.normalize(forward)
local rot = {x = math.asin(forward.y), y = -math.atan2(forward.x, forward.z), z = 0}
local rot = vector.new(math.asin(forward.y), -math.atan2(forward.x, forward.z), 0)
if not up then
return rot
end
@ -237,7 +329,7 @@ function vector.dir_to_rotation(forward, up)
"Invalid vectors passed to vector.dir_to_rotation().")
up = vector.normalize(up)
-- Calculate vector pointing up with roll = 0, just based on forward vector.
local forwup = vector.rotate({x = 0, y = 1, z = 0}, rot)
local forwup = vector.rotate(vector.new(0, 1, 0), rot)
-- 'forwup' and 'up' are now in a plane with 'forward' as normal.
-- The angle between them is the absolute of the roll value we're looking for.
rot.z = vector.angle(forwup, up)

@ -499,10 +499,10 @@ core.register_chatcommand("remove_player", {
-- pos may be a non-integer position
local function find_free_position_near(pos)
local tries = {
{x=1, y=0, z=0},
{x=-1, y=0, z=0},
{x=0, y=0, z=1},
{x=0, y=0, z=-1},
vector.new( 1, 0, 0),
vector.new(-1, 0, 0),
vector.new( 0, 0, 1),
vector.new( 0, 0, -1),
}
for _, d in ipairs(tries) do
local p = vector.add(pos, d)

@ -39,7 +39,7 @@ local gravity = tonumber(core.settings:get("movement_gravity")) or 9.81
core.register_entity(":__builtin:falling_node", {
initial_properties = {
visual = "item",
visual_size = {x = SCALE, y = SCALE, z = SCALE},
visual_size = vector.new(SCALE, SCALE, SCALE),
textures = {},
physical = true,
is_visible = false,
@ -96,7 +96,7 @@ core.register_entity(":__builtin:falling_node", {
local vsize
if def.visual_scale then
local s = def.visual_scale
vsize = {x = s, y = s, z = s}
vsize = vector.new(s, s, s)
end
self.object:set_properties({
is_visible = true,
@ -114,7 +114,7 @@ core.register_entity(":__builtin:falling_node", {
local vsize
if def.visual_scale then
local s = def.visual_scale * SCALE
vsize = {x = s, y = s, z = s}
vsize = vector.new(s, s, s)
end
self.object:set_properties({
is_visible = true,
@ -227,7 +227,7 @@ core.register_entity(":__builtin:falling_node", {
on_activate = function(self, staticdata)
self.object:set_armor_groups({immortal = 1})
self.object:set_acceleration({x = 0, y = -gravity, z = 0})
self.object:set_acceleration(vector.new(0, -gravity, 0))
local ds = core.deserialize(staticdata)
if ds and ds.node then
@ -303,7 +303,7 @@ core.register_entity(":__builtin:falling_node", {
if self.floats then
local pos = self.object:get_pos()
local bcp = vector.round({x = pos.x, y = pos.y - 0.7, z = pos.z})
local bcp = pos:offset(0, -0.7, 0):round()
local bcn = core.get_node(bcp)
local bcd = core.registered_nodes[bcn.name]
@ -344,13 +344,12 @@ core.register_entity(":__builtin:falling_node", {
-- TODO: this hack could be avoided in the future if objects
-- could choose who to collide with
local vel = self.object:get_velocity()
self.object:set_velocity({
x = vel.x,
y = player_collision.old_velocity.y,
z = vel.z
})
self.object:set_pos(vector.add(self.object:get_pos(),
{x = 0, y = -0.5, z = 0}))
self.object:set_velocity(vector.new(
vel.x,
player_collision.old_velocity.y,
vel.z
))
self.object:set_pos(self.object:get_pos():offset(0, -0.5, 0))
end
return
elseif bcn.name == "ignore" then
@ -430,7 +429,7 @@ local function drop_attached_node(p)
if def and def.preserve_metadata then
local oldmeta = core.get_meta(p):to_table().fields
-- Copy pos and node because the callback can modify them.
local pos_copy = {x=p.x, y=p.y, z=p.z}
local pos_copy = vector.new(p)
local node_copy = {name=n.name, param1=n.param1, param2=n.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@ -455,14 +454,14 @@ end
function builtin_shared.check_attached_node(p, n)
local def = core.registered_nodes[n.name]
local d = {x = 0, y = 0, z = 0}
local d = vector.new()
if def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted" then
-- The fallback vector here is in case 'wallmounted to dir' is nil due
-- to voxelmanip placing a wallmounted node without resetting a
-- pre-existing param2 value that is out-of-range for wallmounted.
-- The fallback vector corresponds to param2 = 0.
d = core.wallmounted_to_dir(n.param2) or {x = 0, y = 1, z = 0}
d = core.wallmounted_to_dir(n.param2) or vector.new(0, 1, 0)
else
d.y = -1
end
@ -482,7 +481,7 @@ end
function core.check_single_for_falling(p)
local n = core.get_node(p)
if core.get_item_group(n.name, "falling_node") ~= 0 then
local p_bottom = {x = p.x, y = p.y - 1, z = p.z}
local p_bottom = vector.offset(p, 0, -1, 0)
-- Only spawn falling node if node below is loaded
local n_bottom = core.get_node_or_nil(p_bottom)
local d_bottom = n_bottom and core.registered_nodes[n_bottom.name]
@ -521,17 +520,17 @@ end
-- Down first as likely case, but always before self. The same with sides.
-- Up must come last, so that things above self will also fall all at once.
local check_for_falling_neighbors = {
{x = -1, y = -1, z = 0},
{x = 1, y = -1, z = 0},
{x = 0, y = -1, z = -1},
{x = 0, y = -1, z = 1},
{x = 0, y = -1, z = 0},
{x = -1, y = 0, z = 0},
{x = 1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
{x = 0, y = 0, z = 0},
{x = 0, y = 1, z = 0},
vector.new(-1, -1, 0),
vector.new( 1, -1, 0),
vector.new( 0, -1, -1),
vector.new( 0, -1, 1),
vector.new( 0, -1, 0),
vector.new(-1, 0, 0),
vector.new( 1, 0, 0),
vector.new( 0, 0, 1),
vector.new( 0, 0, -1),
vector.new( 0, 0, 0),
vector.new( 0, 1, 0),
}
function core.check_for_falling(p)

@ -7,8 +7,6 @@ local gamepath = scriptpath .. "game".. DIR_DELIM
-- not exposed to outer context
local builtin_shared = {}
dofile(commonpath .. "vector.lua")
dofile(gamepath .. "constants.lua")
assert(loadfile(gamepath .. "item.lua"))(builtin_shared)
dofile(gamepath .. "register.lua")

@ -92,12 +92,12 @@ end
-- Table of possible dirs
local facedir_to_dir = {
{x= 0, y=0, z= 1},
{x= 1, y=0, z= 0},
{x= 0, y=0, z=-1},
{x=-1, y=0, z= 0},
{x= 0, y=-1, z= 0},
{x= 0, y=1, z= 0},
vector.new( 0, 0, 1),
vector.new( 1, 0, 0),
vector.new( 0, 0, -1),
vector.new(-1, 0, 0),
vector.new( 0, -1, 0),
vector.new( 0, 1, 0),
}
-- Mapping from facedir value to index in facedir_to_dir.
local facedir_to_dir_map = {
@ -136,12 +136,12 @@ end
-- table of dirs in wallmounted order
local wallmounted_to_dir = {
[0] = {x = 0, y = 1, z = 0},
{x = 0, y = -1, z = 0},
{x = 1, y = 0, z = 0},
{x = -1, y = 0, z = 0},
{x = 0, y = 0, z = 1},
{x = 0, y = 0, z = -1},
[0] = vector.new( 0, 1, 0),
vector.new( 0, -1, 0),
vector.new( 1, 0, 0),
vector.new(-1, 0, 0),
vector.new( 0, 0, 1),
vector.new( 0, 0, -1),
}
function core.wallmounted_to_dir(wallmounted)
return wallmounted_to_dir[wallmounted % 8]
@ -152,7 +152,7 @@ function core.dir_to_yaw(dir)
end
function core.yaw_to_dir(yaw)
return {x = -math.sin(yaw), y = 0, z = math.cos(yaw)}
return vector.new(-math.sin(yaw), 0, math.cos(yaw))
end
function core.is_colored_paramtype(ptype)
@ -290,12 +290,12 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end
-- Place above pointed node
local place_to = {x = above.x, y = above.y, z = above.z}
local place_to = vector.new(above)
-- If node under is buildable_to, place into it instead (eg. snow)
if olddef_under.buildable_to then
log("info", "node under is buildable to")
place_to = {x = under.x, y = under.y, z = under.z}
place_to = vector.new(under)
end
if core.is_protected(place_to, playername) then
@ -315,22 +315,14 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
newnode.param2 = def.place_param2
elseif (def.paramtype2 == "wallmounted" or
def.paramtype2 == "colorwallmounted") and not param2 then
local dir = {
x = under.x - above.x,
y = under.y - above.y,
z = under.z - above.z
}
local dir = vector.subtract(under, above)
newnode.param2 = core.dir_to_wallmounted(dir)
-- Calculate the direction for furnaces and chests and stuff
elseif (def.paramtype2 == "facedir" or
def.paramtype2 == "colorfacedir") and not param2 then
local placer_pos = placer and placer:get_pos()
if placer_pos then
local dir = {
x = above.x - placer_pos.x,
y = above.y - placer_pos.y,
z = above.z - placer_pos.z
}
local dir = vector.subtract(above, placer_pos)
newnode.param2 = core.dir_to_facedir(dir)
log("info", "facedir: " .. newnode.param2)
end
@ -384,7 +376,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
-- Run callback
if def.after_place_node and not prevent_after_place then
-- Deepcopy place_to and pointed_thing because callback can modify it
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local place_to_copy = vector.new(place_to)
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
if def.after_place_node(place_to_copy, placer, itemstack,
pointed_thing_copy) then
@ -395,7 +387,7 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
-- Run script hook
for _, callback in ipairs(core.registered_on_placenodes) do
-- Deepcopy pos, node and pointed_thing because callback can modify them
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
local place_to_copy = vector.new(place_to)
local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
local pointed_thing_copy = copy_pointed_thing(pointed_thing)
@ -541,11 +533,11 @@ function core.handle_node_drops(pos, drops, digger)
for _, dropped_item in pairs(drops) do
local left = give_item(dropped_item)
if not left:is_empty() then
local p = {
x = pos.x + math.random()/2-0.25,
y = pos.y + math.random()/2-0.25,
z = pos.z + math.random()/2-0.25,
}
local p = vector.offset(pos,
math.random()/2-0.25,
math.random()/2-0.25,
math.random()/2-0.25
)
core.add_item(p, left)
end
end
@ -604,7 +596,7 @@ function core.node_dig(pos, node, digger)
if def and def.preserve_metadata then
local oldmeta = core.get_meta(pos):to_table().fields
-- Copy pos and node because the callback can modify them.
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
local drop_stacks = {}
for k, v in pairs(drops) do
@ -636,7 +628,7 @@ function core.node_dig(pos, node, digger)
-- Run callback
if def and def.after_dig_node then
-- Copy pos and node because callback can modify them
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
end
@ -649,7 +641,7 @@ function core.node_dig(pos, node, digger)
end
-- Copy pos and node because callback can modify them
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
local pos_copy = vector.new(pos)
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
callback(pos_copy, node_copy, digger)
end
@ -692,7 +684,7 @@ core.nodedef_default = {
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
usable = false,
liquids_pointable = false,
@ -751,7 +743,7 @@ core.craftitemdef_default = {
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,
@ -770,7 +762,7 @@ core.tooldef_default = {
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
wield_scale = vector.new(1, 1, 1),
stack_max = 1,
liquids_pointable = false,
tool_capabilities = nil,
@ -789,7 +781,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items
groups = {},
inventory_image = "",
wield_image = "",
wield_scale = {x=1,y=1,z=1},
wield_scale = vector.new(1, 1, 1),
stack_max = default_stack_max,
liquids_pointable = false,
tool_capabilities = nil,

@ -119,13 +119,12 @@ end
function core.get_position_from_hash(hash)
local pos = {}
pos.x = (hash % 65536) - 32768
local x = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
pos.y = (hash % 65536) - 32768
local y = (hash % 65536) - 32768
hash = math.floor(hash / 65536)
pos.z = (hash % 65536) - 32768
return pos
local z = (hash % 65536) - 32768
return vector.new(x, y, z)
end
@ -215,7 +214,7 @@ function core.is_area_protected(minp, maxp, player_name, interval)
local y = math.floor(yf + 0.5)
for xf = minp.x, maxp.x, d.x do
local x = math.floor(xf + 0.5)
local pos = {x = x, y = y, z = z}
local pos = vector.new(x, y, z)
if core.is_protected(pos, player_name) then
return pos
end

@ -1,6 +1,6 @@
VoxelArea = {
MinEdge = {x=1, y=1, z=1},
MaxEdge = {x=0, y=0, z=0},
MinEdge = vector.new(1, 1, 1),
MaxEdge = vector.new(0, 0, 0),
ystride = 0,
zstride = 0,
}
@ -19,11 +19,11 @@ end
function VoxelArea:getExtent()
local MaxEdge, MinEdge = self.MaxEdge, self.MinEdge
return {
x = MaxEdge.x - MinEdge.x + 1,
y = MaxEdge.y - MinEdge.y + 1,
z = MaxEdge.z - MinEdge.z + 1,
}
return vector.new(
MaxEdge.x - MinEdge.x + 1,
MaxEdge.y - MinEdge.y + 1,
MaxEdge.z - MinEdge.z + 1
)
end
function VoxelArea:getVolume()

@ -30,6 +30,7 @@ local clientpath = scriptdir .. "client" .. DIR_DELIM
local commonpath = scriptdir .. "common" .. DIR_DELIM
local asyncpath = scriptdir .. "async" .. DIR_DELIM
dofile(commonpath .. "vector.lua")
dofile(commonpath .. "strict.lua")
dofile(commonpath .. "serialize.lua")
dofile(commonpath .. "misc_helpers.lua")

@ -2,6 +2,7 @@ _G.core = {}
_G.unpack = table.unpack
_G.serverlistmgr = {}
dofile("builtin/common/vector.lua")
dofile("builtin/common/misc_helpers.lua")
dofile("builtin/mainmenu/serverlistmgr.lua")

@ -1505,6 +1505,9 @@ Position/vector
{x=num, y=num, z=num}
Note: it is highly recommended to construct a vector using the helper function:
vector.new(num, num, num)
For helper functions see [Spatial Vectors].
`pointed_thing`
@ -3168,15 +3171,35 @@ no particular point.
Internally, it is implemented as a table with the 3 fields
`x`, `y` and `z`. Example: `{x = 0, y = 1, z = 0}`.
However, one should *never* create a vector manually as above, such misbehavior
is deprecated. The vector helpers set a metatable for the created vectors which
allows indexing with numbers, calling functions directly on vectors and using
operators (like `+`). Furthermore, the internal implementation might change in
the future.
Old code might still use vectors without metatables, be aware of this!
All these forms of addressing a vector `v` are valid:
`v[1]`, `v[3]`, `v.x`, `v[1] = 42`, `v.y = 13`
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.
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.
For the following functions, `v`, `v1`, `v2` are vectors,
`p1`, `p2` are positions,
`s` is a scalar (a number):
`s` is a scalar (a number),
vectors are written like this: `(x, y, z)`:
* `vector.new(a[, b, c])`:
* `vector.new([a[, b, c]])`:
* Returns a vector.
* A copy of `a` if `a` is a vector.
* `{x = a, y = b, z = c}`, if all of `a`, `b`, `c` are defined numbers.
* `(a, b, c)`, if all of `a`, `b`, `c` are defined numbers.
* `(0, 0, 0)`, if no arguments are given.
* `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.
@ -3189,14 +3212,14 @@ For the following functions, `v`, `v1`, `v2` are vectors,
* Returns a string of the form `"(x, y, z)"`.
* `vector.direction(p1, p2)`:
* Returns a vector of length 1 with direction `p1` to `p2`.
* If `p1` and `p2` are identical, returns `{x = 0, y = 0, z = 0}`.
* 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 `{x = 0, y = 0, z = 0}`.
* If `v` has zero length, returns `(0, 0, 0)`.
* `vector.floor(v)`:
* Returns a vector, each dimension rounded down.
* `vector.round(v)`:
@ -3216,7 +3239,11 @@ For the following functions, `v`, `v1`, `v2` are vectors,
* `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 = x, y = y, z = z}`.
* Returns the sum of the vectors `v` and `(x, y, z)`.
* `vector.check()`:
* 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}`.
For the following functions `x` can be either a vector or a number:
@ -3235,14 +3262,30 @@ For the following functions `x` can be either a vector or a number:
* Returns a scaled vector.
* Deprecated: If `s` is a vector: Returns the Schur quotient.
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: `+` can not be used together with scalars.
* `v1 - v2`:
* Returns the difference of `v1` subtracted by `v2`.
* Note: `-` can not be used together with scalars.
* `v * s` or `s * v`:
* Returns `v` scaled by `s`.
* `v / s`:
* Returns `v` scaled by `1 / s`.
For the following functions `a` is an angle in radians and `r` is a rotation
vector ({x = <pitch>, y = <yaw>, z = <roll>}) where pitch, yaw and roll are
angles in radians.
* `vector.rotate(v, r)`:
* Applies the rotation `r` to `v` and returns the result.
* `vector.rotate({x = 0, y = 0, z = 1}, r)` and
`vector.rotate({x = 0, y = 1, z = 0}, r)` return vectors pointing
* `vector.rotate(vector.new(0, 0, 1), r)` and
`vector.rotate(vector.new(0, 1, 0), r)` return vectors pointing
forward and up relative to an entity's rotation `r`.
* `vector.rotate_around_axis(v1, v2, a)`:
* Returns `v1` rotated around axis `v2` by `a` radians according to

@ -51,6 +51,29 @@ if (value < F1000_MIN || value > F1000_MAX) { \
#define CHECK_POS_TAB(index) CHECK_TYPE(index, "position", LUA_TTABLE)
/**
* A helper which sets (if available) the vector metatable from builtin as metatable
* for the table on top of the stack
*/
static void set_vector_metatable(lua_State *L)
{
// get vector.metatable
lua_getglobal(L, "vector");
if (!lua_istable(L, -1)) {
// there is no global vector table
lua_pop(L, 1);
errorstream << "set_vector_metatable in c_converter.cpp: " <<
"missing global vector table" << std::endl;
return;
}
lua_getfield(L, -1, "metatable");
// set the metatable
lua_setmetatable(L, -3);
// pop vector global
lua_pop(L, 1);
}
void push_float_string(lua_State *L, float value)
{
std::stringstream ss;
@ -69,6 +92,7 @@ void push_v3f(lua_State *L, v3f p)
lua_setfield(L, -2, "y");
lua_pushnumber(L, p.Z);
lua_setfield(L, -2, "z");
set_vector_metatable(L);
}
void push_v2f(lua_State *L, v2f p)
@ -281,6 +305,7 @@ void push_v3s16(lua_State *L, v3s16 p)
lua_setfield(L, -2, "y");
lua_pushinteger(L, p.Z);
lua_setfield(L, -2, "z");
set_vector_metatable(L);
}
v3s16 read_v3s16(lua_State *L, int index)