]> git.lizzy.rs Git - minetest.git/blob - builtin/common/tests/vector_spec.lua
Add vector.combine (#11920)
[minetest.git] / builtin / common / tests / vector_spec.lua
1 _G.vector = {metatable = {}}
2 dofile("builtin/common/vector.lua")
3
4 describe("vector", function()
5         describe("new()", function()
6                 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}))
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})))
14
15                         local input = vector.new({ x = 3, y = 2, z = 1 })
16                         local output = vector.new(input)
17                         assert.same(input, output)
18                         assert.equal(input, output)
19                         assert.is_false(rawequal(input, output))
20                         assert.equal(input, input:new())
21                 end)
22
23                 it("throws on invalid input", function()
24                         assert.has.errors(function()
25                                 vector.new({ x = 3 })
26                         end)
27
28                         assert.has.errors(function()
29                                 vector.new({ d = 3 })
30                         end)
31                 end)
32         end)
33
34         it("zero()", function()
35                 assert.same({x = 0, y = 0, z = 0}, vector.zero())
36                 assert.same(vector.new(), vector.zero())
37                 assert.equal(vector.new(), vector.zero())
38                 assert.is_true(vector.check(vector.zero()))
39         end)
40
41         it("copy()", function()
42                 local v = vector.new(1, 2, 3)
43                 assert.same(v, vector.copy(v))
44                 assert.same(vector.new(v), vector.copy(v))
45                 assert.equal(vector.new(v), vector.copy(v))
46                 assert.is_true(vector.check(vector.copy(v)))
47         end)
48
49         it("indexes", function()
50                 local some_vector = vector.new(24, 42, 13)
51                 assert.equal(24, some_vector[1])
52                 assert.equal(24, some_vector.x)
53                 assert.equal(42, some_vector[2])
54                 assert.equal(42, some_vector.y)
55                 assert.equal(13, some_vector[3])
56                 assert.equal(13, some_vector.z)
57
58                 some_vector[1] = 100
59                 assert.equal(100, some_vector.x)
60                 some_vector.x = 101
61                 assert.equal(101, some_vector[1])
62
63                 some_vector[2] = 100
64                 assert.equal(100, some_vector.y)
65                 some_vector.y = 102
66                 assert.equal(102, some_vector[2])
67
68                 some_vector[3] = 100
69                 assert.equal(100, some_vector.z)
70                 some_vector.z = 103
71                 assert.equal(103, some_vector[3])
72         end)
73
74         it("direction()", function()
75                 local a = vector.new(1, 0, 0)
76                 local b = vector.new(1, 42, 0)
77                 assert.equal(vector.new(0, 1, 0), vector.direction(a, b))
78                 assert.equal(vector.new(0, 1, 0), a:direction(b))
79         end)
80
81         it("distance()", function()
82                 local a = vector.new(1, 0, 0)
83                 local b = vector.new(3, 42, 9)
84                 assert.is_true(math.abs(43 - vector.distance(a, b)) < 1.0e-12)
85                 assert.is_true(math.abs(43 - a:distance(b)) < 1.0e-12)
86                 assert.equal(0, vector.distance(a, a))
87                 assert.equal(0, b:distance(b))
88         end)
89
90         it("length()", function()
91                 local a = vector.new(0, 0, -23)
92                 assert.equal(0, vector.length(vector.new()))
93                 assert.equal(23, vector.length(a))
94                 assert.equal(23, a:length())
95         end)
96
97         it("normalize()", function()
98                 local a = vector.new(0, 0, -23)
99                 assert.equal(vector.new(0, 0, -1), vector.normalize(a))
100                 assert.equal(vector.new(0, 0, -1), a:normalize())
101                 assert.equal(vector.new(), vector.normalize(vector.new()))
102         end)
103
104         it("floor()", function()
105                 local a = vector.new(0.1, 0.9, -0.5)
106                 assert.equal(vector.new(0, 0, -1), vector.floor(a))
107                 assert.equal(vector.new(0, 0, -1), a:floor())
108         end)
109
110         it("round()", function()
111                 local a = vector.new(0.1, 0.9, -0.5)
112                 assert.equal(vector.new(0, 1, -1), vector.round(a))
113                 assert.equal(vector.new(0, 1, -1), a:round())
114         end)
115
116         it("apply()", function()
117                 local i = 0
118                 local f = function(x)
119                         i = i + 1
120                         return x + i
121                 end
122                 local a = vector.new(0.1, 0.9, -0.5)
123                 assert.equal(vector.new(1, 1, 0), vector.apply(a, math.ceil))
124                 assert.equal(vector.new(1, 1, 0), a:apply(math.ceil))
125                 assert.equal(vector.new(0.1, 0.9, 0.5), vector.apply(a, math.abs))
126                 assert.equal(vector.new(0.1, 0.9, 0.5), a:apply(math.abs))
127                 assert.equal(vector.new(1.1, 2.9, 2.5), vector.apply(a, f))
128                 assert.equal(vector.new(4.1, 5.9, 5.5), a:apply(f))
129         end)
130
131         it("combine()", function()
132                 local a = vector.new(1, 2, 3)
133                 local b = vector.new(3, 2, 1)
134                 assert.equal(vector.add(a, b), vector.combine(a, b, function(x, y) return x + y end))
135                 assert.equal(vector.new(3, 2, 3), vector.combine(a, b, math.max))
136                 assert.equal(vector.new(1, 2, 1), vector.combine(a, b, math.min))
137         end)
138
139         it("equals()", function()
140                 local function assertE(a, b)
141                         assert.is_true(vector.equals(a, b))
142                 end
143                 local function assertNE(a, b)
144                         assert.is_false(vector.equals(a, b))
145                 end
146
147                 assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
148                 assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
149                 assertE({x = -1, y = 0, z = 1}, vector.new(-1, 0, 1))
150                 local a = {x = 2, y = 4, z = -10}
151                 assertE(a, a)
152                 assertNE({x = -1, y = 0, z = 1}, a)
153
154                 assert.equal(vector.new(1, 2, 3), vector.new(1, 2, 3))
155                 assert.is_true(vector.new(1, 2, 3):equals(vector.new(1, 2, 3)))
156                 assert.not_equal(vector.new(1, 2, 3), vector.new(1, 2, 4))
157                 assert.is_true(vector.new(1, 2, 3) == vector.new(1, 2, 3))
158                 assert.is_false(vector.new(1, 2, 3) == vector.new(1, 3, 3))
159         end)
160
161         it("metatable is same", function()
162                 local a = vector.new()
163                 local b = vector.new(1, 2, 3)
164
165                 assert.equal(true, vector.check(a))
166                 assert.equal(true, vector.check(b))
167
168                 assert.equal(vector.metatable, getmetatable(a))
169                 assert.equal(vector.metatable, getmetatable(b))
170                 assert.equal(vector.metatable, a.metatable)
171         end)
172
173         it("sort()", function()
174                 local a = vector.new(1, 2, 3)
175                 local b = vector.new(0.5, 232, -2)
176                 local sorted = {vector.new(0.5, 2, -2), vector.new(1, 232, 3)}
177                 assert.same(sorted, {vector.sort(a, b)})
178                 assert.same(sorted, {a:sort(b)})
179         end)
180
181         it("angle()", function()
182                 assert.equal(math.pi, vector.angle(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
183                 assert.equal(math.pi/2, vector.new(0, 1, 0):angle(vector.new(1, 0, 0)))
184         end)
185
186         it("dot()", function()
187                 assert.equal(-14, vector.dot(vector.new(-1, -2, -3), vector.new(1, 2, 3)))
188                 assert.equal(0, vector.new():dot(vector.new(1, 2, 3)))
189         end)
190
191         it("cross()", function()
192                 local a = vector.new(-1, -2, 0)
193                 local b = vector.new(1, 2, 3)
194                 assert.equal(vector.new(-6, 3, 0), vector.cross(a, b))
195                 assert.equal(vector.new(-6, 3, 0), a:cross(b))
196         end)
197
198         it("offset()", function()
199                 assert.same({x = 41, y = 52, z = 63}, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
200                 assert.equal(vector.new(41, 52, 63), vector.offset(vector.new(1, 2, 3), 40, 50, 60))
201                 assert.equal(vector.new(41, 52, 63), vector.new(1, 2, 3):offset(40, 50, 60))
202         end)
203
204         it("is()", function()
205                 local some_table1 = {foo = 13, [42] = 1, "bar", 2}
206                 local some_table2 = {1, 2, 3}
207                 local some_table3 = {x = 1, 2, 3}
208                 local some_table4 = {1, 2, z = 3}
209                 local old = {x = 1, y = 2, z = 3}
210                 local real = vector.new(1, 2, 3)
211
212                 assert.is_false(vector.check(nil))
213                 assert.is_false(vector.check(1))
214                 assert.is_false(vector.check(true))
215                 assert.is_false(vector.check("foo"))
216                 assert.is_false(vector.check(some_table1))
217                 assert.is_false(vector.check(some_table2))
218                 assert.is_false(vector.check(some_table3))
219                 assert.is_false(vector.check(some_table4))
220                 assert.is_false(vector.check(old))
221                 assert.is_true(vector.check(real))
222                 assert.is_true(real:check())
223         end)
224
225         it("global pairs", function()
226                 local out = {}
227                 local vec = vector.new(10, 20, 30)
228                 for k, v in pairs(vec) do
229                         out[k] = v
230                 end
231                 assert.same({x = 10, y = 20, z = 30}, out)
232         end)
233
234         it("abusing works", function()
235                 local v = vector.new(1, 2, 3)
236                 v.a = 1
237                 assert.equal(1, v.a)
238
239                 local a_is_there = false
240                 for key, value in pairs(v) do
241                         if key == "a" then
242                                 a_is_there = true
243                                 assert.equal(value, 1)
244                                 break
245                         end
246                 end
247                 assert.is_true(a_is_there)
248         end)
249
250         it("add()", function()
251                 local a = vector.new(1, 2, 3)
252                 local b = vector.new(1, 4, 3)
253                 local c = vector.new(2, 6, 6)
254                 assert.equal(c, vector.add(a, {x = 1, y = 4, z = 3}))
255                 assert.equal(c, vector.add(a, b))
256                 assert.equal(c, a:add(b))
257                 assert.equal(c, a + b)
258                 assert.equal(c, b + a)
259         end)
260
261         it("subtract()", function()
262                 local a = vector.new(1, 2, 3)
263                 local b = vector.new(2, 4, 3)
264                 local c = vector.new(-1, -2, 0)
265                 assert.equal(c, vector.subtract(a, {x = 2, y = 4, z = 3}))
266                 assert.equal(c, vector.subtract(a, b))
267                 assert.equal(c, a:subtract(b))
268                 assert.equal(c, a - b)
269                 assert.equal(c, -b + a)
270         end)
271
272         it("multiply()", function()
273                 local a = vector.new(1, 2, 3)
274                 local b = vector.new(2, 4, 3)
275                 local c = vector.new(2, 8, 9)
276                 local s = 2
277                 local d = vector.new(2, 4, 6)
278                 assert.equal(c, vector.multiply(a, {x = 2, y = 4, z = 3}))
279                 assert.equal(c, vector.multiply(a, b))
280                 assert.equal(d, vector.multiply(a, s))
281                 assert.equal(d, a:multiply(s))
282                 assert.equal(d, a * s)
283                 assert.equal(d, s * a)
284                 assert.equal(-a, -1 * a)
285         end)
286
287         it("divide()", function()
288                 local a = vector.new(1, 2, 3)
289                 local b = vector.new(2, 4, 3)
290                 local c = vector.new(0.5, 0.5, 1)
291                 local s = 2
292                 local d = vector.new(0.5, 1, 1.5)
293                 assert.equal(c, vector.divide(a, {x = 2, y = 4, z = 3}))
294                 assert.equal(c, vector.divide(a, b))
295                 assert.equal(d, vector.divide(a, s))
296                 assert.equal(d, a:divide(s))
297                 assert.equal(d, a / s)
298                 assert.equal(d, 1/s * a)
299                 assert.equal(-a, a / -1)
300         end)
301
302         it("to_string()", function()
303                 local v = vector.new(1, 2, 3.14)
304                 assert.same("(1, 2, 3.14)", vector.to_string(v))
305                 assert.same("(1, 2, 3.14)", v:to_string())
306                 assert.same("(1, 2, 3.14)", tostring(v))
307         end)
308
309         it("from_string()", function()
310                 local v = vector.new(1, 2, 3.14)
311                 assert.is_true(vector.check(vector.from_string("(1, 2, 3.14)")))
312                 assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")})
313                 assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")})
314                 assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")})
315                 assert.same({v, 11}, {vector.from_string("(1 2 3.14)")})
316                 assert.same({v, 15}, {vector.from_string("( 1, 2, 3.14 )")})
317                 assert.same({v, 15}, {vector.from_string(" ( 1, 2, 3.14) ")})
318                 assert.same({vector.new(), 8}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ")})
319                 assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 8)})
320                 assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 9)})
321                 assert.same(nil, vector.from_string("nothing"))
322         end)
323
324         -- This function is needed because of floating point imprecision.
325         local function almost_equal(a, b)
326                 if type(a) == "number" then
327                         return math.abs(a - b) < 0.00000000001
328                 end
329                 return vector.distance(a, b) < 0.000000000001
330         end
331
332         describe("rotate_around_axis()", function()
333                 it("rotates", function()
334                         assert.True(almost_equal({x = -1, y = 0, z = 0},
335                                 vector.rotate_around_axis({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0}, math.pi)))
336                         assert.True(almost_equal({x = 0, y = 1, z = 0},
337                                 vector.rotate_around_axis({x = 0, y = 0, z = 1}, {x = 1, y = 0, z = 0}, math.pi / 2)))
338                         assert.True(almost_equal({x = 4, y = 1, z = 1},
339                                 vector.rotate_around_axis({x = 4, y = 1, z = 1}, {x = 4, y = 1, z = 1}, math.pi / 6)))
340                 end)
341                 it("keeps distance to axis", function()
342                         local rotate1 = {x = 1, y = 3, z = 1}
343                         local axis1 = {x = 1, y = 3, z = 2}
344                         local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
345                         assert.True(almost_equal(vector.distance(axis1, rotate1), vector.distance(axis1, rotated1)))
346                         local rotate2 = {x = 1, y = 1, z = 3}
347                         local axis2 = {x = 2, y = 6, z = 100}
348                         local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
349                         assert.True(almost_equal(vector.distance(axis2, rotate2), vector.distance(axis2, rotated2)))
350                         local rotate3 = {x = 1, y = -1, z = 3}
351                         local axis3 = {x = 2, y = 6, z = 100}
352                         local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
353                         assert.True(almost_equal(vector.distance(axis3, rotate3), vector.distance(axis3, rotated3)))
354                 end)
355                 it("rotates back", function()
356                         local rotate1 = {x = 1, y = 3, z = 1}
357                         local axis1 = {x = 1, y = 3, z = 2}
358                         local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
359                         rotated1 = vector.rotate_around_axis(rotated1, axis1, -math.pi / 13)
360                         assert.True(almost_equal(rotate1, rotated1))
361                         local rotate2 = {x = 1, y = 1, z = 3}
362                         local axis2 = {x = 2, y = 6, z = 100}
363                         local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
364                         rotated2 = vector.rotate_around_axis(rotated2, axis2, -math.pi / 23)
365                         assert.True(almost_equal(rotate2, rotated2))
366                         local rotate3 = {x = 1, y = -1, z = 3}
367                         local axis3 = {x = 2, y = 6, z = 100}
368                         local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
369                         rotated3 = vector.rotate_around_axis(rotated3, axis3, -math.pi / 2)
370                         assert.True(almost_equal(rotate3, rotated3))
371                 end)
372                 it("is right handed", function()
373                         local v_before1 = {x = 0, y = 1, z = -1}
374                         local v_after1 = vector.rotate_around_axis(v_before1, {x = 1, y = 0, z = 0}, math.pi / 4)
375                         assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
376
377                         local v_before2 = {x = 0, y = 3, z = 4}
378                         local v_after2 = vector.rotate_around_axis(v_before2, {x = 1, y = 0, z = 0},  2 * math.pi / 5)
379                         assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
380
381                         local v_before3 = {x = 1, y = 0, z = -1}
382                         local v_after3 = vector.rotate_around_axis(v_before3, {x = 0, y = 1, z = 0}, math.pi / 4)
383                         assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
384
385                         local v_before4 = {x = 3, y = 0, z = 4}
386                         local v_after4 = vector.rotate_around_axis(v_before4, {x = 0, y = 1, z = 0}, 2 * math.pi / 5)
387                         assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
388
389                         local v_before5 = {x = 1, y = -1, z = 0}
390                         local v_after5 = vector.rotate_around_axis(v_before5, {x = 0, y = 0, z = 1}, math.pi / 4)
391                         assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
392
393                         local v_before6 = {x = 3, y = 4, z = 0}
394                         local v_after6 = vector.rotate_around_axis(v_before6, {x = 0, y = 0, z = 1}, 2 * math.pi / 5)
395                         assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
396                 end)
397         end)
398
399         describe("rotate()", function()
400                 it("rotates", function()
401                         assert.True(almost_equal({x = -1, y = 0, z = 0},
402                                 vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = math.pi, z = 0})))
403                         assert.True(almost_equal({x = 0, y = -1, z = 0},
404                                 vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = 0, z = math.pi / 2})))
405                         assert.True(almost_equal({x = 1, y = 0, z = 0},
406                                 vector.rotate({x = 1, y = 0, z = 0}, {x = math.pi / 123, y = 0, z = 0})))
407                 end)
408                 it("is counterclockwise", function()
409                         local v_before1 = {x = 0, y = 1, z = -1}
410                         local v_after1 = vector.rotate(v_before1, {x = math.pi / 4, y = 0, z = 0})
411                         assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
412
413                         local v_before2 = {x = 0, y = 3, z = 4}
414                         local v_after2 = vector.rotate(v_before2, {x = 2 * math.pi / 5, y = 0, z = 0})
415                         assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
416
417                         local v_before3 = {x = 1, y = 0, z = -1}
418                         local v_after3 = vector.rotate(v_before3, {x = 0, y = math.pi / 4, z = 0})
419                         assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
420
421                         local v_before4 = {x = 3, y = 0, z = 4}
422                         local v_after4 = vector.rotate(v_before4, {x = 0, y = 2 * math.pi / 5, z = 0})
423                         assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
424
425                         local v_before5 = {x = 1, y = -1, z = 0}
426                         local v_after5 = vector.rotate(v_before5, {x = 0, y = 0, z = math.pi / 4})
427                         assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
428
429                         local v_before6 = {x = 3, y = 4, z = 0}
430                         local v_after6 = vector.rotate(v_before6, {x = 0, y = 0, z = 2 * math.pi / 5})
431                         assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
432                 end)
433         end)
434
435         it("dir_to_rotation()", function()
436                 -- Comparing rotations (pitch, yaw, roll) is hard because of certain ambiguities,
437                 -- e.g. (pi, 0, pi) looks exactly the same as (0, pi, 0)
438                 -- So instead we convert the rotation back to vectors and compare these.
439                 local function forward_at_rot(rot)
440                         return vector.rotate(vector.new(0, 0, 1), rot)
441                 end
442                 local function up_at_rot(rot)
443                         return vector.rotate(vector.new(0, 1, 0), rot)
444                 end
445                 local rot1 = vector.dir_to_rotation({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0})
446                 assert.True(almost_equal({x = 1, y = 0, z = 0}, forward_at_rot(rot1)))
447                 assert.True(almost_equal({x = 0, y = 1, z = 0}, up_at_rot(rot1)))
448                 local rot2 = vector.dir_to_rotation({x = 1, y = 1, z = 0}, {x = 0, y = 0, z = 1})
449                 assert.True(almost_equal({x = 1/math.sqrt(2), y = 1/math.sqrt(2), z = 0}, forward_at_rot(rot2)))
450                 assert.True(almost_equal({x = 0, y = 0, z = 1}, up_at_rot(rot2)))
451                 for i = 1, 1000 do
452                         local rand_vec = vector.new(math.random(), math.random(), math.random())
453                         if vector.length(rand_vec) ~= 0 then
454                                 local rot_1 = vector.dir_to_rotation(rand_vec)
455                                 local rot_2 = {
456                                         x = math.atan2(rand_vec.y, math.sqrt(rand_vec.z * rand_vec.z + rand_vec.x * rand_vec.x)),
457                                         y = -math.atan2(rand_vec.x, rand_vec.z),
458                                         z = 0
459                                 }
460                                 assert.True(almost_equal(rot_1, rot_2))
461                         end
462                 end
463
464         end)
465 end)