1 _G.vector = {metatable = {}}
2 dofile("builtin/common/vector.lua")
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}))
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})))
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())
23 it("throws on invalid input", function()
24 assert.has.errors(function()
28 assert.has.errors(function()
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()))
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)))
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)
59 assert.equal(100, some_vector.x)
61 assert.equal(101, some_vector[1])
64 assert.equal(100, some_vector.y)
66 assert.equal(102, some_vector[2])
69 assert.equal(100, some_vector.z)
71 assert.equal(103, some_vector[3])
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))
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))
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())
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()))
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())
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())
116 it("apply()", function()
118 local f = function(x)
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))
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))
139 it("equals()", function()
140 local function assertE(a, b)
141 assert.is_true(vector.equals(a, b))
143 local function assertNE(a, b)
144 assert.is_false(vector.equals(a, b))
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}
152 assertNE({x = -1, y = 0, z = 1}, a)
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))
161 it("metatable is same", function()
162 local a = vector.new()
163 local b = vector.new(1, 2, 3)
165 assert.equal(true, vector.check(a))
166 assert.equal(true, vector.check(b))
168 assert.equal(vector.metatable, getmetatable(a))
169 assert.equal(vector.metatable, getmetatable(b))
170 assert.equal(vector.metatable, a.metatable)
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)})
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)))
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)))
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))
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))
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)
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())
225 it("global pairs", function()
227 local vec = vector.new(10, 20, 30)
228 for k, v in pairs(vec) do
231 assert.same({x = 10, y = 20, z = 30}, out)
234 it("abusing works", function()
235 local v = vector.new(1, 2, 3)
239 local a_is_there = false
240 for key, value in pairs(v) do
243 assert.equal(value, 1)
247 assert.is_true(a_is_there)
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)
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)
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)
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)
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)
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)
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))
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"))
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
329 return vector.distance(a, b) < 0.000000000001
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)))
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)))
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))
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}))
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}))
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}))
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}))
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}))
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}))
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})))
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}))
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}))
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}))
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}))
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}))
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}))
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)
442 local function up_at_rot(rot)
443 return vector.rotate(vector.new(0, 1, 0), rot)
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)))
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)
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),
460 assert.True(almost_equal(rot_1, rot_2))