Tests: Compare vectors with epsilon
[blender-staging.git] / tests / python / bl_pyapi_mathutils.py
1 # Apache License, Version 2.0
2
3 # ./blender.bin --background -noaudio --python tests/python/bl_pyapi_mathutils.py -- --verbose
4 import unittest
5 from mathutils import Matrix, Vector, Quaternion
6 from mathutils import kdtree
7 import math
8
9 # keep globals immutable
10 vector_data = (
11     (1.0, 0.0, 0.0),
12     (0.0, 1.0, 0.0),
13     (0.0, 0.0, 1.0),
14
15     (1.0, 1.0, 1.0),
16
17     (0.33783, 0.715698, -0.611206),
18     (-0.944031, -0.326599, -0.045624),
19     (-0.101074, -0.416443, -0.903503),
20     (0.799286, 0.49411, -0.341949),
21     (-0.854645, 0.518036, 0.033936),
22     (0.42514, -0.437866, -0.792114),
23     (-0.358948, 0.597046, 0.717377),
24     (-0.985413,0.144714, 0.089294),
25     )
26
27 # get data at different scales
28 vector_data = sum(
29     (tuple(tuple(a * scale for a in v) for v in vector_data)
30     for scale in (s * sign for s in (0.0001, 0.1, 1.0, 10.0, 1000.0, 100000.0)
31                            for sign in (1.0, -1.0))), ()) + ((0.0, 0.0, 0.0),)
32
33
34 class MatrixTesting(unittest.TestCase):
35     def test_matrix_column_access(self):
36         #mat =
37         #[ 1  2  3  4 ]
38         #[ 1  2  3  4 ]
39         #[ 1  2  3  4 ]
40         mat = Matrix(((1, 11, 111),
41                       (2, 22, 222),
42                       (3, 33, 333),
43                       (4, 44, 444)))
44
45         self.assertEqual(mat[0], Vector((1, 11, 111)))
46         self.assertEqual(mat[1], Vector((2, 22, 222)))
47         self.assertEqual(mat[2], Vector((3, 33, 333)))
48         self.assertEqual(mat[3], Vector((4, 44, 444)))
49
50     def test_item_access(self):
51         args = ((1, 4, 0, -1),
52                 (2, -1, 2, -2),
53                 (0, 3, 8, 3),
54                 (-2, 9, 1, 0))
55
56         mat = Matrix(args)
57
58         for row in range(4):
59             for col in range(4):
60                 self.assertEqual(mat[row][col], args[row][col])
61
62         self.assertEqual(mat[0][2], 0)
63         self.assertEqual(mat[3][1], 9)
64         self.assertEqual(mat[2][3], 3)
65         self.assertEqual(mat[0][0], 1)
66         self.assertEqual(mat[3][3], 0)
67
68     def test_item_assignment(self):
69         mat = Matrix() - Matrix()
70         indices = (0, 0), (1, 3), (2, 0), (3, 2), (3, 1)
71         checked_indices = []
72         for row, col in indices:
73             mat[row][col] = 1
74
75         for row in range(4):
76             for col in range(4):
77                 if mat[row][col]:
78                     checked_indices.append((row, col))
79
80         for item in checked_indices:
81             self.assertIn(item, indices)
82
83     def test_matrix_to_3x3(self):
84         #mat =
85         #[ 1  2  3  4  ]
86         #[ 2  4  6  8  ]
87         #[ 3  6  9  12 ]
88         #[ 4  8  12 16 ]
89         mat = Matrix(tuple((i, 2 * i, 3 * i, 4 * i) for i in range(1, 5)))
90         mat_correct = Matrix(((1, 2, 3), (2, 4, 6), (3, 6, 9)))
91         self.assertEqual(mat.to_3x3(), mat_correct)
92
93     def test_matrix_to_translation(self):
94         mat = Matrix()
95         mat[0][3] = 1
96         mat[1][3] = 2
97         mat[2][3] = 3
98         self.assertEqual(mat.to_translation(), Vector((1, 2, 3)))
99
100     def test_matrix_translation(self):
101         mat = Matrix()
102         mat.translation = Vector((1, 2, 3))
103         self.assertEqual(mat[0][3], 1)
104         self.assertEqual(mat[1][3], 2)
105         self.assertEqual(mat[2][3], 3)
106
107     def test_non_square_mult(self):
108         mat1 = Matrix(((1, 2, 3),
109                        (4, 5, 6)))
110         mat2 = Matrix(((1, 2),
111                        (3, 4),
112                        (5, 6)))
113
114         prod_mat1 = Matrix(((22, 28),
115                             (49, 64)))
116         prod_mat2 = Matrix(((9, 12, 15),
117                             (19, 26, 33),
118                             (29, 40, 51)))
119
120         self.assertEqual(mat1 * mat2, prod_mat1)
121         self.assertEqual(mat2 * mat1, prod_mat2)
122
123     def test_mat4x4_vec3D_mult(self):
124         mat = Matrix(((1, 0, 2, 0),
125                       (0, 6, 0, 0),
126                       (0, 0, 1, 1),
127                       (0, 0, 0, 1)))
128
129         vec = Vector((1, 2, 3))
130         
131         prod_mat_vec = Vector((7, 12, 4))
132         prod_vec_mat = Vector((1, 12, 5))
133
134         self.assertEqual(mat * vec, prod_mat_vec)
135         self.assertEqual(vec * mat, prod_vec_mat)
136
137     def test_mat_vec_mult(self):
138         mat1 = Matrix()
139
140         vec = Vector((1, 2))
141
142         self.assertRaises(ValueError, mat1.__mul__, vec)
143         self.assertRaises(ValueError, vec.__mul__, mat1)
144
145         mat2 = Matrix(((1, 2),
146                        (-2, 3)))
147
148         prod = Vector((5, 4))
149
150         self.assertEqual(mat2 * vec, prod)
151
152     def test_matrix_inverse(self):
153         mat = Matrix(((1, 4, 0, -1),
154                       (2, -1, 2, -2),
155                       (0, 3, 8, 3),
156                       (-2, 9, 1, 0)))
157
158         inv_mat = (1 / 285) * Matrix(((195, -57, 27, -102),
159                                       (50, -19, 4, 6),
160                                       (-60, 57, 18, 27),
161                                       (110, -133, 43, -78)))
162
163         self.assertEqual(mat.inverted(), inv_mat)
164
165     def test_matrix_inverse_safe(self):
166         mat = Matrix(((1, 4, 0, -1),
167                       (2, -1, 0, -2),
168                       (0, 3, 0, 3),
169                       (-2, 9, 0, 0)))
170
171         # Warning, if we change epsilon in py api we have to update this!!!
172         epsilon = 1e-8
173         inv_mat_safe = mat.copy()
174         inv_mat_safe[0][0] += epsilon
175         inv_mat_safe[1][1] += epsilon
176         inv_mat_safe[2][2] += epsilon
177         inv_mat_safe[3][3] += epsilon
178         inv_mat_safe.invert()
179         '''
180         inv_mat_safe = Matrix(((1.0, -0.5, 0.0, -0.5),
181                                (0.222222, -0.111111, -0.0, 0.0),
182                                (-333333344.0, 316666656.0, 100000000.0,  150000000.0),
183                                (0.888888, -0.9444444, 0.0, -0.5)))
184         '''
185
186         self.assertEqual(mat.inverted_safe(), inv_mat_safe)
187
188     def test_matrix_mult(self):
189         mat = Matrix(((1, 4, 0, -1),
190                       (2, -1, 2, -2),
191                       (0, 3, 8, 3),
192                       (-2, 9, 1, 0)))
193
194         prod_mat = Matrix(((11, -9, 7, -9),
195                            (4, -3, 12, 6),
196                            (0, 48, 73, 18),
197                            (16, -14, 26, -13)))
198
199         self.assertEqual(mat * mat, prod_mat)
200
201
202 class VectorTesting(unittest.TestCase):
203
204     def test_orthogonal(self):
205
206         angle_90d = math.pi / 2.0
207         for v in vector_data:
208             v = Vector(v)
209             if v.length_squared != 0.0:
210                 self.assertAlmostEqual(v.angle(v.orthogonal()), angle_90d)
211
212
213 class QuaternionTesting(unittest.TestCase):
214
215     def test_to_expmap(self):
216         q = Quaternion((0, 0, 1), math.radians(90))
217
218         e = q.to_exponential_map()
219         self.assertAlmostEqual(e.x, 0)
220         self.assertAlmostEqual(e.y, 0)
221         self.assertAlmostEqual(e.z, math.radians(90), 6)
222
223     def test_expmap_axis_normalization(self):
224         q = Quaternion((1, 1, 0), 2)
225         e = q.to_exponential_map()
226
227         self.assertAlmostEqual(e.x, 2 * math.sqrt(0.5), 6)
228         self.assertAlmostEqual(e.y, 2 * math.sqrt(0.5), 6)
229         self.assertAlmostEqual(e.z, 0)
230
231     def test_from_expmap(self):
232         e = Vector((1, 1, 0))
233         q = Quaternion(e)
234         axis, angle = q.to_axis_angle()
235
236         self.assertAlmostEqual(angle, math.sqrt(2), 6)
237         self.assertAlmostEqual(axis.x, math.sqrt(0.5), 6)
238         self.assertAlmostEqual(axis.y, math.sqrt(0.5), 6)
239         self.assertAlmostEqual(axis.z, 0)
240
241
242 class KDTreeTesting(unittest.TestCase):
243     @staticmethod
244     def kdtree_create_grid_3d_data(tot):
245         index = 0
246         mul = 1.0 / (tot - 1)
247         for x in range(tot):
248             for y in range(tot):
249                 for z in range(tot):
250                     yield (x * mul, y * mul, z * mul), index
251                     index += 1
252
253     @staticmethod
254     def kdtree_create_grid_3d(tot, *, filter_fn=None):
255         k = kdtree.KDTree(tot * tot * tot)
256         for co, index in KDTreeTesting.kdtree_create_grid_3d_data(tot):
257             if (filter_fn is not None) and (not filter_fn(co, index)):
258                 continue
259             k.insert(co, index)
260         k.balance()
261         return k
262
263     def assertAlmostEqualVector(self, first, second, places=7, msg=None, delta=None):
264         self.assertAlmostEqual(first[0], second[0], places=places, msg=msg, delta=delta)
265         self.assertAlmostEqual(first[1], second[1], places=places, msg=msg, delta=delta)
266         self.assertAlmostEqual(first[2], second[2], places=places, msg=msg, delta=delta)
267
268     def test_kdtree_single(self):
269         co = (0,) * 3
270         index = 2
271
272         k = kdtree.KDTree(1)
273         k.insert(co, index)
274         k.balance()
275
276         co_found, index_found, dist_found = k.find(co)
277
278         self.assertEqual(tuple(co_found), co)
279         self.assertEqual(index_found, index)
280         self.assertEqual(dist_found, 0.0)
281
282     def test_kdtree_empty(self):
283         co = (0,) * 3
284
285         k = kdtree.KDTree(0)
286         k.balance()
287
288         co_found, index_found, dist_found = k.find(co)
289
290         self.assertIsNone(co_found)
291         self.assertIsNone(index_found)
292         self.assertIsNone(dist_found)
293
294     def test_kdtree_line(self):
295         tot = 10
296
297         k = kdtree.KDTree(tot)
298
299         for i in range(tot):
300             k.insert((i,) * 3, i)
301
302         k.balance()
303
304         co_found, index_found, dist_found = k.find((-1,) * 3)
305         self.assertEqual(tuple(co_found), (0,) * 3)
306
307         co_found, index_found, dist_found = k.find((tot,) * 3)
308         self.assertEqual(tuple(co_found), (tot - 1,) * 3)
309
310     def test_kdtree_grid(self):
311         size = 10
312         k = self.kdtree_create_grid_3d(size)
313
314         # find_range
315         ret = k.find_range((0.5,) * 3, 2.0)
316         self.assertEqual(len(ret), size * size * size)
317
318         ret = k.find_range((1.0,) * 3, 1.0 / size)
319         self.assertEqual(len(ret), 1)
320
321         ret = k.find_range((1.0,) * 3, 2.0 / size)
322         self.assertEqual(len(ret), 8)
323
324         ret = k.find_range((10,) * 3, 0.5)
325         self.assertEqual(len(ret), 0)
326
327         # find_n
328         tot = 0
329         ret = k.find_n((1.0,) * 3, tot)
330         self.assertEqual(len(ret), tot)
331
332         tot = 10
333         ret = k.find_n((1.0,) * 3, tot)
334         self.assertEqual(len(ret), tot)
335         self.assertEqual(ret[0][2], 0.0)
336
337         tot = size * size * size
338         ret = k.find_n((1.0,) * 3, tot)
339         self.assertEqual(len(ret), tot)
340
341     def test_kdtree_grid_filter_simple(self):
342         size = 10
343         k = self.kdtree_create_grid_3d(size)
344
345         # filter exact index
346         ret_regular = k.find((1.0,) * 3)
347         ret_filter = k.find((1.0,) * 3, filter=lambda i: i == ret_regular[1])
348         self.assertEqual(ret_regular, ret_filter)
349         ret_filter = k.find((-1.0,) * 3, filter=lambda i: i == ret_regular[1])
350         self.assertEqual(ret_regular[:2], ret_filter[:2])  # ignore distance
351
352     def test_kdtree_grid_filter_pairs(self):
353         size = 10
354         k_all = self.kdtree_create_grid_3d(size)
355         k_odd = self.kdtree_create_grid_3d(size, filter_fn=lambda co, i: (i % 2) == 1)
356         k_evn = self.kdtree_create_grid_3d(size, filter_fn=lambda co, i: (i % 2) == 0)
357
358         samples = 5
359         mul = 1 / (samples - 1)
360         for x in range(samples):
361             for y in range(samples):
362                 for z in range(samples):
363                     co = (x * mul, y * mul, z * mul)
364
365                     ret_regular = k_odd.find(co)
366                     self.assertEqual(ret_regular[1] % 2, 1)
367                     ret_filter = k_all.find(co, lambda i: (i % 2) == 1)
368                     self.assertAlmostEqualVector(ret_regular, ret_filter)
369
370                     ret_regular = k_evn.find(co)
371                     self.assertEqual(ret_regular[1] % 2, 0)
372                     ret_filter = k_all.find(co, lambda i: (i % 2) == 0)
373                     self.assertAlmostEqualVector(ret_regular, ret_filter)
374
375
376         # filter out all values (search odd tree for even values and the reverse)
377         co = (0,) * 3
378         ret_filter = k_odd.find(co, lambda i: (i % 2) == 0)
379         self.assertEqual(ret_filter[1], None)
380
381         ret_filter = k_evn.find(co, lambda i: (i % 2) == 1)
382         self.assertEqual(ret_filter[1], None)
383
384     def test_kdtree_invalid_size(self):
385         with self.assertRaises(ValueError):
386             kdtree.KDTree(-1)
387
388     def test_kdtree_invalid_balance(self):
389         co = (0,) * 3
390         index = 2
391
392         k = kdtree.KDTree(2)
393         k.insert(co, index)
394         k.balance()
395         k.insert(co, index)
396         with self.assertRaises(RuntimeError):
397             k.find(co)
398
399     def test_kdtree_invalid_filter(self):
400         k = kdtree.KDTree(1)
401         k.insert((0,) * 3, 0)
402         k.balance()
403         # not callable
404         with self.assertRaises(TypeError):
405             k.find((0,) * 3, filter=None)
406         # no args
407         with self.assertRaises(TypeError):
408             k.find((0,) * 3, filter=lambda: None)
409         # bad return value
410         with self.assertRaises(ValueError):
411             k.find((0,) * 3, filter=lambda i: None)
412
413
414 if __name__ == '__main__':
415     import sys
416     sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
417     unittest.main()