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 local input = vector.new({ x = 3, y = 2, z = 1 })
12 local output = vector.new(input)
13 assert.same(input, output)
14 assert.are_not.equal(input, output)
17 it("throws on invalid input", function()
18 assert.has.errors(function()
22 assert.has.errors(function()
28 it("equal()", function()
29 local function assertE(a, b)
30 assert.is_true(vector.equals(a, b))
32 local function assertNE(a, b)
33 assert.is_false(vector.equals(a, b))
36 assertE({x = 0, y = 0, z = 0}, {x = 0, y = 0, z = 0})
37 assertE({x = -1, y = 0, z = 1}, {x = -1, y = 0, z = 1})
38 local a = { x = 2, y = 4, z = -10 }
40 assertNE({x = -1, y = 0, z = 1}, a)
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 }))
47 it("offset()", function()
48 assert.same({ x = 41, y = 52, z = 63 }, vector.offset(vector.new(1, 2, 3), 40, 50, 60))
51 it("to_string()", function()
52 local v = vector.new(1, 2, 3.14)
53 assert.same("(1, 2, 3.14)", vector.to_string(v))
56 it("from_string()", function()
57 local v = vector.new(1, 2, 3.14)
58 assert.same({v, 13}, {vector.from_string("(1, 2, 3.14)")})
59 assert.same({v, 12}, {vector.from_string("(1,2 ,3.14)")})
60 assert.same({v, 12}, {vector.from_string("(1,2,3.14,)")})
61 assert.same({v, 11}, {vector.from_string("(1 2 3.14)")})
62 assert.same({v, 15}, {vector.from_string("( 1, 2, 3.14 )")})
63 assert.same({v, 15}, {vector.from_string(" ( 1, 2, 3.14) ")})
64 assert.same({vector.new(), 8}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ")})
65 assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 8)})
66 assert.same({v, 22}, {vector.from_string("(0,0,0) ( 1, 2, 3.14) ", 9)})
67 assert.same(nil, vector.from_string("nothing"))
70 -- This function is needed because of floating point imprecision.
71 local function almost_equal(a, b)
72 if type(a) == "number" then
73 return math.abs(a - b) < 0.00000000001
75 return vector.distance(a, b) < 0.000000000001
78 describe("rotate_around_axis()", function()
79 it("rotates", function()
80 assert.True(almost_equal({x = -1, y = 0, z = 0},
81 vector.rotate_around_axis({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0}, math.pi)))
82 assert.True(almost_equal({x = 0, y = 1, z = 0},
83 vector.rotate_around_axis({x = 0, y = 0, z = 1}, {x = 1, y = 0, z = 0}, math.pi / 2)))
84 assert.True(almost_equal({x = 4, y = 1, z = 1},
85 vector.rotate_around_axis({x = 4, y = 1, z = 1}, {x = 4, y = 1, z = 1}, math.pi / 6)))
87 it("keeps distance to axis", function()
88 local rotate1 = {x = 1, y = 3, z = 1}
89 local axis1 = {x = 1, y = 3, z = 2}
90 local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
91 assert.True(almost_equal(vector.distance(axis1, rotate1), vector.distance(axis1, rotated1)))
92 local rotate2 = {x = 1, y = 1, z = 3}
93 local axis2 = {x = 2, y = 6, z = 100}
94 local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
95 assert.True(almost_equal(vector.distance(axis2, rotate2), vector.distance(axis2, rotated2)))
96 local rotate3 = {x = 1, y = -1, z = 3}
97 local axis3 = {x = 2, y = 6, z = 100}
98 local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
99 assert.True(almost_equal(vector.distance(axis3, rotate3), vector.distance(axis3, rotated3)))
101 it("rotates back", function()
102 local rotate1 = {x = 1, y = 3, z = 1}
103 local axis1 = {x = 1, y = 3, z = 2}
104 local rotated1 = vector.rotate_around_axis(rotate1, axis1, math.pi / 13)
105 rotated1 = vector.rotate_around_axis(rotated1, axis1, -math.pi / 13)
106 assert.True(almost_equal(rotate1, rotated1))
107 local rotate2 = {x = 1, y = 1, z = 3}
108 local axis2 = {x = 2, y = 6, z = 100}
109 local rotated2 = vector.rotate_around_axis(rotate2, axis2, math.pi / 23)
110 rotated2 = vector.rotate_around_axis(rotated2, axis2, -math.pi / 23)
111 assert.True(almost_equal(rotate2, rotated2))
112 local rotate3 = {x = 1, y = -1, z = 3}
113 local axis3 = {x = 2, y = 6, z = 100}
114 local rotated3 = vector.rotate_around_axis(rotate3, axis3, math.pi / 2)
115 rotated3 = vector.rotate_around_axis(rotated3, axis3, -math.pi / 2)
116 assert.True(almost_equal(rotate3, rotated3))
118 it("is right handed", function()
119 local v_before1 = {x = 0, y = 1, z = -1}
120 local v_after1 = vector.rotate_around_axis(v_before1, {x = 1, y = 0, z = 0}, math.pi / 4)
121 assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
123 local v_before2 = {x = 0, y = 3, z = 4}
124 local v_after2 = vector.rotate_around_axis(v_before2, {x = 1, y = 0, z = 0}, 2 * math.pi / 5)
125 assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
127 local v_before3 = {x = 1, y = 0, z = -1}
128 local v_after3 = vector.rotate_around_axis(v_before3, {x = 0, y = 1, z = 0}, math.pi / 4)
129 assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
131 local v_before4 = {x = 3, y = 0, z = 4}
132 local v_after4 = vector.rotate_around_axis(v_before4, {x = 0, y = 1, z = 0}, 2 * math.pi / 5)
133 assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
135 local v_before5 = {x = 1, y = -1, z = 0}
136 local v_after5 = vector.rotate_around_axis(v_before5, {x = 0, y = 0, z = 1}, math.pi / 4)
137 assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
139 local v_before6 = {x = 3, y = 4, z = 0}
140 local v_after6 = vector.rotate_around_axis(v_before6, {x = 0, y = 0, z = 1}, 2 * math.pi / 5)
141 assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
145 describe("rotate()", function()
146 it("rotates", function()
147 assert.True(almost_equal({x = -1, y = 0, z = 0},
148 vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = math.pi, z = 0})))
149 assert.True(almost_equal({x = 0, y = -1, z = 0},
150 vector.rotate({x = 1, y = 0, z = 0}, {x = 0, y = 0, z = math.pi / 2})))
151 assert.True(almost_equal({x = 1, y = 0, z = 0},
152 vector.rotate({x = 1, y = 0, z = 0}, {x = math.pi / 123, y = 0, z = 0})))
154 it("is counterclockwise", function()
155 local v_before1 = {x = 0, y = 1, z = -1}
156 local v_after1 = vector.rotate(v_before1, {x = math.pi / 4, y = 0, z = 0})
157 assert.True(almost_equal(vector.normalize(vector.cross(v_after1, v_before1)), {x = 1, y = 0, z = 0}))
159 local v_before2 = {x = 0, y = 3, z = 4}
160 local v_after2 = vector.rotate(v_before2, {x = 2 * math.pi / 5, y = 0, z = 0})
161 assert.True(almost_equal(vector.normalize(vector.cross(v_after2, v_before2)), {x = 1, y = 0, z = 0}))
163 local v_before3 = {x = 1, y = 0, z = -1}
164 local v_after3 = vector.rotate(v_before3, {x = 0, y = math.pi / 4, z = 0})
165 assert.True(almost_equal(vector.normalize(vector.cross(v_after3, v_before3)), {x = 0, y = 1, z = 0}))
167 local v_before4 = {x = 3, y = 0, z = 4}
168 local v_after4 = vector.rotate(v_before4, {x = 0, y = 2 * math.pi / 5, z = 0})
169 assert.True(almost_equal(vector.normalize(vector.cross(v_after4, v_before4)), {x = 0, y = 1, z = 0}))
171 local v_before5 = {x = 1, y = -1, z = 0}
172 local v_after5 = vector.rotate(v_before5, {x = 0, y = 0, z = math.pi / 4})
173 assert.True(almost_equal(vector.normalize(vector.cross(v_after5, v_before5)), {x = 0, y = 0, z = 1}))
175 local v_before6 = {x = 3, y = 4, z = 0}
176 local v_after6 = vector.rotate(v_before6, {x = 0, y = 0, z = 2 * math.pi / 5})
177 assert.True(almost_equal(vector.normalize(vector.cross(v_after6, v_before6)), {x = 0, y = 0, z = 1}))
181 it("dir_to_rotation()", function()
182 -- Comparing rotations (pitch, yaw, roll) is hard because of certain ambiguities,
183 -- e.g. (pi, 0, pi) looks exactly the same as (0, pi, 0)
184 -- So instead we convert the rotation back to vectors and compare these.
185 local function forward_at_rot(rot)
186 return vector.rotate(vector.new(0, 0, 1), rot)
188 local function up_at_rot(rot)
189 return vector.rotate(vector.new(0, 1, 0), rot)
191 local rot1 = vector.dir_to_rotation({x = 1, y = 0, z = 0}, {x = 0, y = 1, z = 0})
192 assert.True(almost_equal({x = 1, y = 0, z = 0}, forward_at_rot(rot1)))
193 assert.True(almost_equal({x = 0, y = 1, z = 0}, up_at_rot(rot1)))
194 local rot2 = vector.dir_to_rotation({x = 1, y = 1, z = 0}, {x = 0, y = 0, z = 1})
195 assert.True(almost_equal({x = 1/math.sqrt(2), y = 1/math.sqrt(2), z = 0}, forward_at_rot(rot2)))
196 assert.True(almost_equal({x = 0, y = 0, z = 1}, up_at_rot(rot2)))
198 local rand_vec = vector.new(math.random(), math.random(), math.random())
199 if vector.length(rand_vec) ~= 0 then
200 local rot_1 = vector.dir_to_rotation(rand_vec)
202 x = math.atan2(rand_vec.y, math.sqrt(rand_vec.z * rand_vec.z + rand_vec.x * rand_vec.x)),
203 y = -math.atan2(rand_vec.x, rand_vec.z),
206 assert.True(almost_equal(rot_1, rot_2))