Skip to content

Commit 8f085e0

Browse files
authored
Add metatables to lua vectors (#11039)
Add backwards-compatible metatable functions for vectors.
1 parent e15cae9 commit 8f085e0

File tree

16 files changed

+571
-180
lines changed

16 files changed

+571
-180
lines changed

builtin/client/init.lua

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ dofile(clientpath .. "register.lua")
77
dofile(commonpath .. "after.lua")
88
dofile(commonpath .. "chatcommands.lua")
99
dofile(clientpath .. "chatcommands.lua")
10-
dofile(commonpath .. "vector.lua")
1110
dofile(clientpath .. "death_formspec.lua")

builtin/common/misc_helpers.lua

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -432,21 +432,19 @@ function core.string_to_pos(value)
432432
return nil
433433
end
434434

435-
local p = {}
436-
p.x, p.y, p.z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
437-
if p.x and p.y and p.z then
438-
p.x = tonumber(p.x)
439-
p.y = tonumber(p.y)
440-
p.z = tonumber(p.z)
441-
return p
442-
end
443-
p = {}
444-
p.x, p.y, p.z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
445-
if p.x and p.y and p.z then
446-
p.x = tonumber(p.x)
447-
p.y = tonumber(p.y)
448-
p.z = tonumber(p.z)
449-
return p
435+
local x, y, z = string.match(value, "^([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+)$")
436+
if x and y and z then
437+
x = tonumber(x)
438+
y = tonumber(y)
439+
z = tonumber(z)
440+
return vector.new(x, y, z)
441+
end
442+
x, y, z = string.match(value, "^%( *([%d.-]+)[, ] *([%d.-]+)[, ] *([%d.-]+) *%)$")
443+
if x and y and z then
444+
x = tonumber(x)
445+
y = tonumber(y)
446+
z = tonumber(z)
447+
return vector.new(x, y, z)
450448
end
451449
return nil
452450
end

builtin/common/tests/misc_helpers_spec.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
_G.core = {}
2+
dofile("builtin/common/vector.lua")
23
dofile("builtin/common/misc_helpers.lua")
34

45
describe("string", function()
@@ -55,8 +56,8 @@ end)
5556

5657
describe("pos", function()
5758
it("from string", function()
58-
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("10.0, 5.1, -2"))
59-
assert.same({ x = 10, y = 5.1, z = -2}, core.string_to_pos("( 10.0, 5.1, -2)"))
59+
assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("10.0, 5.1, -2"))
60+
assert.equal(vector.new(10, 5.1, -2), core.string_to_pos("( 10.0, 5.1, -2)"))
6061
assert.is_nil(core.string_to_pos("asd, 5, -2)"))
6162
end)
6263

builtin/common/tests/serialize_spec.lua

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ _G.core = {}
33
_G.setfenv = require 'busted.compatibility'.setfenv
44

55
dofile("builtin/common/serialize.lua")
6+
dofile("builtin/common/vector.lua")
67

78
describe("serialize", function()
89
it("works", function()
@@ -53,4 +54,16 @@ describe("serialize", function()
5354
assert.is_nil(test_out.func)
5455
assert.equals(test_out.foo, "bar")
5556
end)
57+
58+
it("vectors work", function()
59+
local v = vector.new(1, 2, 3)
60+
assert.same({{x = 1, y = 2, z = 3}}, core.deserialize(core.serialize({v})))
61+
assert.same({x = 1, y = 2, z = 3}, core.deserialize(core.serialize(v)))
62+
63+
-- abuse
64+
v = vector.new(1, 2, 3)
65+
v.a = "bla"
66+
assert.same({x = 1, y = 2, z = 3, a = "bla"},
67+
core.deserialize(core.serialize(v)))
68+
end)
5669
end)

builtin/common/tests/vector_spec.lua

Lines changed: 239 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@ dofile("builtin/common/vector.lua")
44
describe("vector", function()
55
describe("new()", function()
66
it("constructs", function()
7-
assert.same({ x = 0, y = 0, z = 0 }, vector.new())
8-
assert.same({ x = 1, y = 2, z = 3 }, vector.new(1, 2, 3))
9-
assert.same({ x = 3, y = 2, z = 1 }, vector.new({ x = 3, y = 2, z = 1 }))
7+
assert.same({x = 0, y = 0, z = 0}, vector.new())
8+
assert.same({x = 1, y = 2, z = 3}, vector.new(1, 2, 3))
9+
assert.same({x = 3, y = 2, z = 1}, vector.new({x = 3, y = 2, z = 1}))
10+
11+
assert.is_true(vector.check(vector.new()))
12+
assert.is_true(vector.check(vector.new(1, 2, 3)))
13+
assert.is_true(vector.check(vector.new({x = 3, y = 2, z = 1})))
1014

1115
local input = vector.new({ x = 3, y = 2, z = 1 })
1216
local output = vector.new(input)
1317
assert.same(input, output)
14-
assert.are_not.equal(input, output)
18+
assert.equal(input, output)
19+
assert.is_false(rawequal(input, output))
20+
assert.equal(input, input:new())
1521
end)
1622

1723
it("throws on invalid input", function()
@@ -25,7 +31,89 @@ describe("vector", function()
2531
end)
2632
end)
2733

28-
it("equal()", function()
34+
it("indexes", function()
35+
local some_vector = vector.new(24, 42, 13)
36+
assert.equal(24, some_vector[1])
37+
assert.equal(24, some_vector.x)
38+
assert.equal(42, some_vector[2])
39+
assert.equal(42, some_vector.y)
40+
assert.equal(13, some_vector[3])
41+
assert.equal(13, some_vector.z)
42+
43+
some_vector[1] = 100
44+
assert.equal(100, some_vector.x)
45+
some_vector.x = 101
46+
assert.equal(101, some_vector[1])
47+
48+
some_vector[2] = 100
49+
assert.equal(100, some_vector.y)
50+
some_vector.y = 102
51+
assert.equal(102, some_vector[2])
52+
53+
some_vector[3] = 100
54+
assert.equal(100, some_vector.z)
55+
some_vector.z = 103
56+
assert.equal(103, some_vector[3])
57+
end)
58+
59+
it("direction()", function()
60+
local a = vector.new(1, 0, 0)
61+
local b = vector.new(1, 42, 0)
62+
assert.equal(vector.new(0, 1, 0), vector.direction(a, b))
63+
assert.equal(vector.new(0, 1, 0), a:direction(b))
64+
end)
65+
66+
it("distance()", function()
67+
local a = vector.new(1, 0, 0)
68+
local b = vector.new(3, 42, 9)
69+
assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12)
70+
assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12)
71+
assert.equal(0, vector.distance(a, a))
72+
assert.equal(0, b:distance(b))
73+
end)
74+
75+
it("length()", function()
76+
local a = vector.new(0, 0, -23)
77+
assert.equal(0, vector.length(vector.new()))
78+
assert.equal(23, vector.length(a))
79+
assert.equal(23, a:length())
80+
end)
81+
82+
it("normalize()", function()
83+
local a = vector.new(0, 0, -23)
84+
assert.equal(vector.new(0, 0, -1), vector.normalize(a))
85+
assert.equal(vector.new(0, 0, -1), a:normalize())
86+
assert.equal(vector.new(), vector.normalize(vector.new()))
87+
end)
88+
89+
it("floor()", function()
90+
local a = vector.new(0.1, 0.9, -0.5)
91+
assert.equal(vector.new(0, 0, -1), vector.floor(a))
92+
assert.equal(vector.new(0, 0, -1), a:floor())
93+
end)
94+
95+
it("round()", function()
96+
local a = vector.new(0.1, 0.9, -0.5)
97+
assert.equal(vector.new(0, 1, -1), vector.round(a))
98+
assert.equal(vector.new(0, 1, -1), a:round())
99+
end)
100+
101+
it("apply()", function()
102+
local i = 0
103+
local f = function(x)
104+
i = i + 1
105+
return x + i
106+
end
107+
local a = vector.new(0.1, 0.9, -0.5)
108+
assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil))
109+
assert.equal(vector.new(1, 1, 0), a:apply(math.ceil))
110+
assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs))
111+
assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs))
112+
assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f))
113+
assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
114+
end)
115+
116+
it("equals()", function()
29117
local function assertE(a, b)
30118
assert.is_true(vector.equals(a, b))
31119
end
@@ -35,22 +123,164 @@ describe("vector", function()
35123

36124
assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
37125
assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
38-
local a = { x = 2, y = 4, z = -10 }
126+
assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1))
127+
local a = {x = 2, y = 4, z = -10}
39128
assertE(a, a)
40129
assertNE({x = -1, y = 0, z = 1}, a)
130+
131+
assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3))
132+
assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3)))
133+
assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4))
134+
assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3))
135+
assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3))
41136
end)
42137

43-
it("add()", function()
44-
assert.same({ x = 2, y = 4, z = 6 }, vector.add(vector.new(1, 2, 3), { x = 1, y = 2, z = 3 }))
138+
it("metatable is same", function()
139+
local a = vector.new()
140+
local b = vector.new(1, 2, 3)
141+
142+
assert.equal(true, vector.check(a))
143+
assert.equal(true, vector.check(b))
144+
145+
assert.equal(vector.metatable, getmetatable(a))
146+
assert.equal(vector.metatable, getmetatable(b))
147+
assert.equal(vector.metatable, a.metatable)
148+
end)
149+
150+
it("sort()", function()
151+
local a = vector.new(1, 2, 3)
152+
local b = vector.new(0.5, 232, -2)
153+
local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)}
154+
assert.same(sorted, {vector.sort(a, b)})
155+
assert.same(sorted, {a:sort(b)})
156+
end)
157+
158+
it("angle()", function()
159+
assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
160+
assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0)))
161+
end)
162+
163+
it("dot()", function()
164+
assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
165+
assert.equal(0, vector.new():dot(vector.new(1, 2, 3)))
166+
end)
167+
168+
it("cross()", function()
169+
local a = vector.new(-1, -2, 0)
170+
local b = vector.new(1, 2, 3)
171+
assert.equal(vector.new(-6, 3, 0), vector.cross(a, b))
172+
assert.equal(vector.new(-6, 3, 0), a:cross(b))
45173
end)
46174

47175
it("offset()", function()
48-
assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
176+
assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
177+
assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60))
178+
assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60))
179+
end)
180+
181+
it("is()", function()
182+
local some_table1 = {foo = 13, [42] = 1, "bar", 2}
183+
local some_table2 = {1, 2, 3}
184+
local some_table3 = {x = 1, 2, 3}
185+
local some_table4 = {1, 2, z = 3}
186+
local old = {x = 1, y = 2, z = 3}
187+
local real = vector.new(1, 2, 3)
188+
189+
assert.is_false(vector.check(nil))
190+
assert.is_false(vector.check(1))
191+
assert.is_false(vector.check(true))
192+
assert.is_false(vector.check("foo"))
193+
assert.is_false(vector.check(some_table1))
194+
assert.is_false(vector.check(some_table2))
195+
assert.is_false(vector.check(some_table3))
196+
assert.is_false(vector.check(some_table4))
197+
assert.is_false(vector.check(old))
198+
assert.is_true(vector.check(real))
199+
assert.is_true(real:check())
200+
end)
201+
202+
it("global pairs", function()
203+
local out = {}
204+
local vec = vector.new(10, 20, 30)
205+
for k, v in pairs(vec) do
206+
out[k] = v
207+
end
208+
assert.same({x = 10, y = 20, z = 30}, out)
209+
end)
210+
211+
it("abusing works", function()
212+
local v = vector.new(1, 2, 3)
213+
v.a = 1
214+
assert.equal(1, v.a)
215+
216+
local a_is_there = false
217+
for key, value in pairs(v) do
218+
if key == "a" then
219+
a_is_there = true
220+
assert.equal(value, 1)
221+
break
222+
end
223+
end
224+
assert.is_true(a_is_there)
225+
end)
226+
227+
it("add()", function()
228+
local a = vector.new(1, 2, 3)
229+
local b = vector.new(1, 4, 3)
230+
local c = vector.new(2, 6, 6)
231+
assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3}))
232+
assert.equal(c, vector.add(a, b))
233+
assert.equal(c, a:add(b))
234+
assert.equal(c, a + b)
235+
assert.equal(c, b + a)
236+
end)
237+
238+
it("subtract()", function()
239+
local a = vector.new(1, 2, 3)
240+
local b = vector.new(2, 4, 3)
241+
local c = vector.new(-1, -2, 0)
242+
assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3}))
243+
assert.equal(c, vector.subtract(a, b))
244+
assert.equal(c, a:subtract(b))
245+
assert.equal(c, a - b)
246+
assert.equal(c, -b + a)
247+
end)
248+
249+
it("multiply()", function()
250+
local a = vector.new(1, 2, 3)
251+
local b = vector.new(2, 4, 3)
252+
local c = vector.new(2, 8, 9)
253+
local s = 2
254+
local d = vector.new(2, 4, 6)
255+
assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3}))
256+
assert.equal(c, vector.multiply(a, b))
257+
assert.equal(d, vector.multiply(a, s))
258+
assert.equal(d, a:multiply(s))
259+
assert.equal(d, a * s)
260+
assert.equal(d, s * a)
261+
assert.equal(-a, -1 * a)
262+
end)
263+
264+
it("divide()", function()
265+
local a = vector.new(1, 2, 3)
266+
local b = vector.new(2, 4, 3)
267+
local c = vector.new(0.5, 0.5, 1)
268+
local s = 2
269+
local d = vector.new(0.5, 1, 1.5)
270+
assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3}))
271+
assert.equal(c, vector.divide(a, b))
272+
assert.equal(d, vector.divide(a, s))
273+
assert.equal(d, a:divide(s))
274+
assert.equal(d, a / s)
275+
assert.equal(d, 1/s * a)
276+
assert.equal(-a, a / -1)
49277
end)
50278

51279
it("to_string()", function()
52280
local v = vector.new(1, 2, 3.14)
53281
assert.same("(1, 2, 3.14)", vector.to_string(v))
282+
assert.same("(1, 2, 3.14)", v:to_string())
283+
assert.same("(1, 2, 3.14)", tostring(v))
54284
end)
55285

56286
it("from_string()", function()

0 commit comments

Comments
 (0)