Blender V5.0
BLI_math_matrix_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "testing/testing.h"
6
7#include "BLI_array.hh"
8#include "BLI_math_base.h"
9#include "BLI_math_matrix.h"
10#include "BLI_math_matrix.hh"
11#include "BLI_math_rotation.h"
12#include "BLI_math_rotation.hh"
13#include "BLI_rand.hh"
14
15TEST(math_matrix, interp_m4_m4m4_regular)
16{
17 /* Test 4x4 matrix interpolation without singularity, i.e. without axis flip. */
18
19 /* Transposed matrix, so that the code here is written in the same way as print_m4() outputs. */
20 /* This matrix represents T=(0.1, 0.2, 0.3), R=(40, 50, 60) degrees, S=(0.7, 0.8, 0.9) */
21 float matrix_a[4][4] = {
22 {0.224976f, -0.333770f, 0.765074f, 0.100000f},
23 {0.389669f, 0.647565f, 0.168130f, 0.200000f},
24 {-0.536231f, 0.330541f, 0.443163f, 0.300000f},
25 {0.000000f, 0.000000f, 0.000000f, 1.000000f},
26 };
27 transpose_m4(matrix_a);
28
29 float matrix_i[4][4];
30 unit_m4(matrix_i);
31
32 float result[4][4];
33 const float epsilon = 1e-6;
34 interp_m4_m4m4(result, matrix_i, matrix_a, 0.0f);
35 EXPECT_M4_NEAR(result, matrix_i, epsilon);
36
37 interp_m4_m4m4(result, matrix_i, matrix_a, 1.0f);
38 EXPECT_M4_NEAR(result, matrix_a, epsilon);
39
40 /* This matrix is based on the current implementation of the code, and isn't guaranteed to be
41 * correct. It's just consistent with the current implementation. */
42 float matrix_halfway[4][4] = {
43 {0.690643f, -0.253244f, 0.484996f, 0.050000f},
44 {0.271924f, 0.852623f, 0.012348f, 0.100000f},
45 {-0.414209f, 0.137484f, 0.816778f, 0.150000f},
46 {0.000000f, 0.000000f, 0.000000f, 1.000000f},
47 };
48
49 transpose_m4(matrix_halfway);
50 interp_m4_m4m4(result, matrix_i, matrix_a, 0.5f);
51 EXPECT_M4_NEAR(result, matrix_halfway, epsilon);
52}
53
54TEST(math_matrix, interp_m3_m3m3_singularity)
55{
56 /* A singularity means that there is an axis mirror in the rotation component of the matrix.
57 * This is reflected in its negative determinant.
58 *
59 * The interpolation of 4x4 matrices performs linear interpolation on the translation component,
60 * and then uses the 3x3 interpolation function to handle rotation and scale. As a result, this
61 * test for a singularity in the rotation matrix only needs to test the 3x3 case. */
62
63 /* Transposed matrix, so that the code here is written in the same way as print_m4() outputs. */
64 /* This matrix represents R=(4, 5, 6) degrees, S=(-1, 1, 1) */
65 float matrix_a[3][3] = {
66 {-0.990737f, -0.098227f, 0.093759f},
67 {-0.104131f, 0.992735f, -0.060286f},
68 {0.087156f, 0.069491f, 0.993768f},
69 };
70 transpose_m3(matrix_a);
71 EXPECT_NEAR(-1.0f, determinant_m3_array(matrix_a), 1e-6);
72
73 /* This matrix represents R=(0, 0, 0), S=(-1, 1, 1) */
74 float matrix_b[3][3] = {
75 {-1.0f, 0.0f, 0.0f},
76 {0.0f, 1.0f, 0.0f},
77 {0.0f, 0.0f, 1.0f},
78 };
79 transpose_m3(matrix_b);
80
81 float result[3][3];
82 interp_m3_m3m3(result, matrix_a, matrix_b, 0.0f);
83 EXPECT_M3_NEAR(result, matrix_a, 1e-5);
84
85 interp_m3_m3m3(result, matrix_a, matrix_b, 1.0f);
86 EXPECT_M3_NEAR(result, matrix_b, 1e-5);
87
88 interp_m3_m3m3(result, matrix_a, matrix_b, 0.5f);
89 float expect[3][3] = {
90 {-0.997681f, -0.049995f, 0.046186f},
91 {-0.051473f, 0.998181f, -0.031385f},
92 {0.044533f, 0.033689f, 0.998440f},
93 };
94 transpose_m3(expect);
95 EXPECT_M3_NEAR(result, expect, 1e-5);
96
97 /* Interpolating between a matrix with and without axis flip can cause it to go through a zero
98 * point. The determinant det(A) of a matrix represents the change in volume; interpolating
99 * between matrices with det(A)=-1 and det(B)=1 will have to go through a point where
100 * det(result)=0, so where the volume becomes zero. */
101 float matrix_i[3][3];
102 unit_m3(matrix_i);
103 zero_m3(expect);
104 interp_m3_m3m3(result, matrix_a, matrix_i, 0.5f);
105 EXPECT_NEAR(0.0f, determinant_m3_array(result), 1e-5);
106 EXPECT_M3_NEAR(result, expect, 1e-5);
107}
108
109TEST(math_matrix, mul_m3_series)
110{
111 float matrix[3][3] = {
112 {2.0f, 0.0f, 0.0f},
113 {0.0f, 3.0f, 0.0f},
114 {0.0f, 0.0f, 5.0f},
115 };
116 mul_m3_series(matrix, matrix, matrix, matrix);
117 float const expect[3][3] = {
118 {8.0f, 0.0f, 0.0f},
119 {0.0f, 27.0f, 0.0f},
120 {0.0f, 0.0f, 125.0f},
121 };
122 EXPECT_M3_NEAR(matrix, expect, 1e-5);
123}
124
125TEST(math_matrix, mul_m4_series)
126{
127 float matrix[4][4] = {
128 {2.0f, 0.0f, 0.0f, 0.0f},
129 {0.0f, 3.0f, 0.0f, 0.0f},
130 {0.0f, 0.0f, 5.0f, 0.0f},
131 {0.0f, 0.0f, 0.0f, 7.0f},
132 };
133 mul_m4_series(matrix, matrix, matrix, matrix);
134 float const expect[4][4] = {
135 {8.0f, 0.0f, 0.0f, 0.0f},
136 {0.0f, 27.0f, 0.0f, 0.0f},
137 {0.0f, 0.0f, 125.0f, 0.0f},
138 {0.0f, 0.0f, 0.0f, 343.0f},
139 };
140 EXPECT_M4_NEAR(matrix, expect, 1e-5);
141}
142
143namespace blender::tests {
144
145using namespace blender::math;
146
147TEST(math_matrix, MatrixInverse)
148{
150 float3x3 inv = invert(mat);
151 float3x3 expect = float3x3({0.5f, 0.0f, 0.0f}, {0.0f, 0.5f, 0.0f}, {0.0f, 0.0f, 0.5f});
152 EXPECT_M3_NEAR(inv, expect, 1e-5f);
153
154 bool success;
155 float3x3 mat2 = float3x3::all(1);
156 float3x3 inv2 = invert(mat2, success);
157 float3x3 expect2 = float3x3::all(0);
158 EXPECT_M3_NEAR(inv2, expect2, 1e-5f);
159 EXPECT_FALSE(success);
160}
161
162TEST(math_matrix, MatrixPseudoInverse)
163{
164 float4x4 mat = transpose(float4x4({0.224976f, -0.333770f, 0.765074f, 0.100000f},
165 {0.389669f, 0.647565f, 0.168130f, 0.200000f},
166 {-0.536231f, 0.330541f, 0.443163f, 0.300000f},
167 {0.000000f, 0.000000f, 0.000000f, 1.000000f}));
168 float4x4 expect = transpose(float4x4({0.224976f, -0.333770f, 0.765074f, 0.100000f},
169 {0.389669f, 0.647565f, 0.168130f, 0.200000f},
170 {-0.536231f, 0.330541f, 0.443163f, 0.300000f},
171 {0.000000f, 0.000000f, 0.000000f, 1.000000f}));
172 /* MSVC 2019 has issues deducing the right template parameters, use an explicit template
173 * instantiating to sidestep the issue. */
175 pseudoinverse_m4_m4(expect.ptr(), mat.ptr(), 1e-8f);
176 EXPECT_M4_NEAR(inv, expect, 1e-5f);
177
178 float4x4 mat2 = transpose(float4x4({0.000000f, -0.333770f, 0.765074f, 0.100000f},
179 {0.000000f, 0.647565f, 0.168130f, 0.200000f},
180 {0.000000f, 0.330541f, 0.443163f, 0.300000f},
181 {0.000000f, 0.000000f, 0.000000f, 1.000000f}));
182 float4x4 expect2 = transpose(float4x4({0.000000f, 0.000000f, 0.000000f, 0.000000f},
183 {-0.51311f, 1.02638f, 0.496437f, -0.302896f},
184 {0.952803f, 0.221885f, 0.527413f, -0.297881f},
185 {-0.0275438f, -0.0477073f, 0.0656508f, 0.9926f}));
186 /* MSVC 2019 has issues deducing the right template parameters, use an explicit template
187 * instantiating to sidestep the issue. */
189 EXPECT_M4_NEAR(inv2, expect2, 1e-5f);
190}
191
192TEST(math_matrix, MatrixDeterminant)
193{
194 float2x2 m2({1, 2}, {3, 4});
195 float3x3 m3({1, 2, 3}, {-3, 4, -5}, {5, -6, 7});
196 float4x4 m4({1, 2, -3, 3}, {3, 4, -5, 3}, {5, 6, 7, -3}, {5, 6, 7, 1});
197 EXPECT_NEAR(determinant(m2), -2.0f, 1e-8f);
198 EXPECT_NEAR(determinant(m3), -16.0f, 1e-8f);
199 EXPECT_NEAR(determinant(m4), -112.0f, 1e-8f);
200 EXPECT_NEAR(determinant(double2x2(m2)), -2.0f, 1e-8f);
201 EXPECT_NEAR(determinant(double3x3(m3)), -16.0f, 1e-8f);
202 EXPECT_NEAR(determinant(double4x4(m4)), -112.0f, 1e-8f);
203}
204
205TEST(math_matrix, MatrixAdjoint)
206{
207 float2x2 m2({1, 2}, {3, 4});
208 float3x3 m3({1, 2, 3}, {-3, 4, -5}, {5, -6, 7});
209 float4x4 m4({1, 2, -3, 3}, {3, 4, -5, 3}, {5, 6, 7, -3}, {5, 6, 7, 1});
210 float2x2 expect2 = transpose(float2x2({4, -3}, {-2, 1}));
211 float3x3 expect3 = transpose(float3x3({-2, -4, -2}, {-32, -8, 16}, {-22, -4, 10}));
212 float4x4 expect4 = transpose(
213 float4x4({232, -184, -8, -0}, {-128, 88, 16, 0}, {80, -76, 4, 28}, {-72, 60, -12, -28}));
214 EXPECT_M2_NEAR(adjoint(m2), expect2, 1e-8f);
215 EXPECT_M3_NEAR(adjoint(m3), expect3, 1e-8f);
216 EXPECT_M4_NEAR(adjoint(m4), expect4, 1e-8f);
217}
218
219TEST(math_matrix, MatrixAccess)
220{
221 float4x4 m({1, 2, 3, 4}, {5, 6, 7, 8}, {9, 1, 2, 3}, {4, 5, 6, 7});
223 EXPECT_EQ(m.x_axis(), float3(1, 2, 3));
224 EXPECT_EQ(m.y_axis(), float3(5, 6, 7));
225 EXPECT_EQ(m.z_axis(), float3(9, 1, 2));
226 EXPECT_EQ(m.location(), float3(4, 5, 6));
227}
228
229TEST(math_matrix, MatrixInit)
230{
231 float4x4 expect;
232
233 float4x4 m = from_location<float4x4>({1, 2, 3});
234 expect = float4x4({1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {1, 2, 3, 1});
235 EXPECT_TRUE(is_equal(m, expect, 0.00001f));
236
237 expect = transpose(float4x4({0.411982, -0.833738, -0.36763, 0},
238 {-0.0587266, -0.426918, 0.902382, 0},
239 {-0.909297, -0.350175, -0.224845, 0},
240 {0, 0, 0, 1}));
241 EulerXYZ euler(1, 2, 3);
242 Quaternion quat = to_quaternion(euler);
243 AxisAngle axis_angle = to_axis_angle(euler);
244 m = from_rotation<float4x4>(euler);
245 EXPECT_M3_NEAR(m, expect, 1e-5);
246 m = from_rotation<float4x4>(quat);
247 EXPECT_M3_NEAR(m, expect, 1e-5);
248 m = from_rotation<float4x4>(axis_angle);
249 EXPECT_M3_NEAR(m, expect, 1e-5);
250
251 expect = transpose(float4x4({0.823964, -1.66748, -0.735261, 3.28334},
252 {-0.117453, -0.853835, 1.80476, 5.44925},
253 {-1.81859, -0.700351, -0.44969, -0.330972},
254 {0, 0, 0, 1}));
255 DualQuaternion dual_quat(quat, Quaternion(0.5f, 0.5f, 0.5f, 1.5f), float4x4::diagonal(2.0f));
256 m = from_rotation<float4x4>(dual_quat);
257 EXPECT_M3_NEAR(m, expect, 1e-5);
258
259 m = from_scale<float4x4>(float4(1, 2, 3, 4));
260 expect = float4x4({1, 0, 0, 0}, {0, 2, 0, 0}, {0, 0, 3, 0}, {0, 0, 0, 4});
261 EXPECT_TRUE(is_equal(m, expect, 0.00001f));
262
263 m = from_scale<float4x4>(float3(1, 2, 3));
264 expect = float4x4({1, 0, 0, 0}, {0, 2, 0, 0}, {0, 0, 3, 0}, {0, 0, 0, 1});
265 EXPECT_TRUE(is_equal(m, expect, 0.00001f));
266
267 m = from_scale<float4x4>(float2(1, 2));
268 expect = float4x4({1, 0, 0, 0}, {0, 2, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1});
269 EXPECT_TRUE(is_equal(m, expect, 0.00001f));
270
271 m = from_loc_rot<float4x4>({1, 2, 3}, EulerXYZ{1, 2, 3});
272 expect = float4x4({0.411982, -0.0587266, -0.909297, 0},
273 {-0.833738, -0.426918, -0.350175, 0},
274 {-0.36763, 0.902382, -0.224845, 0},
275 {1, 2, 3, 1});
276 EXPECT_TRUE(is_equal(m, expect, 0.00001f));
277
278 m = from_loc_rot_scale<float4x4>({1, 2, 3}, EulerXYZ{1, 2, 3}, float3{1, 2, 3});
279 expect = float4x4({0.411982, -0.0587266, -0.909297, 0},
280 {-1.66748, -0.853835, -0.700351, 0},
281 {-1.10289, 2.70714, -0.674535, 0},
282 {1, 2, 3, 1});
283 EXPECT_TRUE(is_equal(m, expect, 0.00001f));
284
285 float3 up = normalize(float3(1, 2, 3));
287 /* Output is not expected to be stable. Just test if they satisfy the expectations. */
288 EXPECT_EQ(m.z_axis(), up);
289 EXPECT_LE(abs(dot(m.z_axis(), m.x_axis())), 0.000001f);
290 EXPECT_LE(abs(dot(m.y_axis(), m.x_axis())), 0.000001f);
291 EXPECT_LE(abs(dot(m.z_axis(), m.y_axis())), 0.000001f);
292 EXPECT_NEAR(1.0f, determinant(m), 1e-6);
293}
294
295TEST(math_matrix, MatrixModify)
296{
297 const float epsilon = 1e-6;
298 float4x4 result, expect;
299 float4x4 m1 = float4x4({0, 3, 0, 0}, {2, 0, 0, 0}, {0, 0, 2, 0}, {0, 0, 0, 1});
300
301 expect = float4x4({0, 3, 0, 0}, {2, 0, 0, 0}, {0, 0, 2, 0}, {4, 9, 2, 1});
302 result = translate(m1, float3(3, 2, 1));
303 EXPECT_M4_NEAR(result, expect, epsilon);
304
305 expect = float4x4({0, 3, 0, 0}, {2, 0, 0, 0}, {0, 0, 2, 0}, {4, 0, 0, 1});
306 result = translate(m1, float2(0, 2));
307 EXPECT_M4_NEAR(result, expect, epsilon);
308
309 expect = float4x4({0, 0, -2, 0}, {2, 0, 0, 0}, {0, 3, 0, 0}, {0, 0, 0, 1});
310 result = rotate(m1, AxisAngle({0, 1, 0}, M_PI_2));
311 EXPECT_M4_NEAR(result, expect, epsilon);
312
313 expect = float4x4({0, 9, 0, 0}, {4, 0, 0, 0}, {0, 0, 8, 0}, {0, 0, 0, 1});
314 result = scale(m1, float3(3, 2, 4));
315 EXPECT_M4_NEAR(result, expect, epsilon);
316
317 expect = float4x4({0, 9, 0, 0}, {4, 0, 0, 0}, {0, 0, 2, 0}, {0, 0, 0, 1});
318 result = scale(m1, float2(3, 2));
319 EXPECT_M4_NEAR(result, expect, epsilon);
320}
321
322TEST(math_matrix, MatrixCompareTest)
323{
324 float4x4 m1 = float4x4({0, 3, 0, 0}, {2, 0, 0, 0}, {0, 0, 2, 0}, {0, 0, 0, 1});
325 float4x4 m2 = float4x4({0, 3.001, 0, 0}, {1.999, 0, 0, 0}, {0, 0, 2.001, 0}, {0, 0, 0, 1.001});
326 float4x4 m3 = float4x4({0, 3.001, 0, 0}, {1, 1, 0, 0}, {0, 0, 2.001, 0}, {0, 0, 0, 1.001});
327 float4x4 m4 = float4x4({0, 1, 0, 0}, {1, 0, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1});
328 float4x4 m5 = float4x4({0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0});
329 float4x4 m6 = float4x4({1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1});
330 EXPECT_TRUE(is_equal(m1, m2, 0.01f));
331 EXPECT_FALSE(is_equal(m1, m2, 0.0001f));
332 EXPECT_FALSE(is_equal(m1, m3, 0.01f));
333 EXPECT_TRUE(is_orthogonal(m1));
334 EXPECT_FALSE(is_orthogonal(m3));
335 EXPECT_TRUE(is_orthonormal(m4));
336 EXPECT_FALSE(is_orthonormal(m1));
337 EXPECT_FALSE(is_orthonormal(m3));
338 EXPECT_FALSE(is_uniformly_scaled(m1));
339 EXPECT_TRUE(is_uniformly_scaled(m4));
340 EXPECT_FALSE(is_zero(m4));
341 EXPECT_TRUE(is_zero(m5));
342 EXPECT_TRUE(is_negative(m4));
343 EXPECT_FALSE(is_negative(m5));
344 EXPECT_FALSE(is_negative(m6));
345}
346
347TEST(math_matrix, MatrixMultiply)
348{
349
350 {
351 const float4x4 matrix_a = {
352 {1.0, 2.0, 3.0, 4.0},
353 {5.0, 6.0, 7.0, 8.0},
354 {9.0, 10.0, 11.0, 12.0},
355 {13.0, 14.0, 15.0, 16.0},
356 };
357 const float4x4 matrix_b = {
358 {0.1f, 0.2f, 0.3f, 0.4f},
359 {0.5f, 0.6f, 0.7f, 0.8f},
360 {0.9f, 1.0f, 1.1f, 1.2f},
361 {1.3f, 1.4f, 1.5f, 1.6f},
362 };
363
364 const float4x4 expected = {
365 {9.0f, 10.0f, 11.0f, 12.0f},
366 {20.2f, 22.8f, 25.4f, 28.0f},
367 {31.4f, 35.6f, 39.8f, 44.0f},
368 {42.6f, 48.4f, 54.2f, 60.0f},
369 };
370
371 const float4x4 result = matrix_a * matrix_b;
372
373 EXPECT_M4_NEAR(result, expected, 1e-5f);
374 }
375}
376
377TEST(math_matrix, MatrixToNearestEuler)
378{
379 EulerXYZ eul1 = EulerXYZ(225.08542, -1.12485, -121.23738);
380 Euler3 eul2 = Euler3(float3{4.06112, 100.561928, -18.9063}, EulerOrder::ZXY);
381
382 float3x3 mat = {{0.808309, -0.578051, -0.111775},
383 {0.47251, 0.750174, -0.462572},
384 {0.351241, 0.321087, 0.879507}};
385
386 EXPECT_V3_NEAR(float3(to_nearest_euler(mat, eul1)), float3(225.71, 0.112009, -120.001), 1e-3);
387 EXPECT_V3_NEAR(float3(to_nearest_euler(mat, eul2)), float3(5.95631, 100.911, -19.5061), 1e-3);
388}
389
390TEST(math_matrix, MatrixMethods)
391{
392 float4x4 m = float4x4({0, 3, 0, 0}, {2, 0, 0, 0}, {0, 0, 2, 0}, {0, 1, 0, 1});
393 auto expect_eul = EulerXYZ(0, 0, M_PI_2);
394 auto expect_qt = Quaternion(0, -M_SQRT1_2, M_SQRT1_2, 0);
395 float3 expect_scale = float3(3, 2, 2);
396 float3 expect_location = float3(0, 1, 0);
397
398 EXPECT_EQ(to_scale(m), expect_scale);
399
400 float4 expect_size = {3, 2, 2, M_SQRT2};
401 float4 size;
403 EXPECT_TRUE(is_unit_scale(m1));
404 EXPECT_V4_NEAR(size, expect_size, 0.0002f);
405
406 float4x4 m2 = normalize(m);
407 EXPECT_TRUE(is_unit_scale(m2));
408
409 EXPECT_V3_NEAR(float3(to_euler(m1)), float3(expect_eul), 0.0002f);
410 EXPECT_V4_NEAR(float4(to_quaternion(m1)), float4(expect_qt), 0.0002f);
411
412 EulerXYZ eul;
413 Quaternion qt;
415 to_rot_scale(float3x3(m), eul, scale);
416 to_rot_scale(float3x3(m), qt, scale);
417 EXPECT_V3_NEAR(scale, expect_scale, 0.00001f);
418 EXPECT_V4_NEAR(float4(qt), float4(expect_qt), 0.0002f);
419 EXPECT_V3_NEAR(float3(eul), float3(expect_eul), 0.0002f);
420
421 float3 loc;
422 to_loc_rot_scale(m, loc, eul, scale);
423 to_loc_rot_scale(m, loc, qt, scale);
424 EXPECT_V3_NEAR(scale, expect_scale, 0.00001f);
425 EXPECT_V3_NEAR(loc, expect_location, 0.00001f);
426 EXPECT_V4_NEAR(float4(qt), float4(expect_qt), 0.0002f);
427 EXPECT_V3_NEAR(float3(eul), float3(expect_eul), 0.0002f);
428}
429
430TEST(math_matrix, Transformation2DMatrixDecomposition)
431{
432 const float2 translation = float2(1.0f, 2.0f);
433 const AngleRadian rotation = AngleRadian(0.5f);
434 const float2 scale = float2(5.0f, 3.0f);
435
436 const float3x3 transformation = from_loc_rot_scale<float3x3>(translation, rotation, scale);
437
438 AngleRadian decomposed_rotation;
439 float2 decomposed_translation, decomposed_scale;
440 to_loc_rot_scale(transformation, decomposed_translation, decomposed_rotation, decomposed_scale);
441
442 EXPECT_V2_NEAR(decomposed_translation, translation, 0.00001f);
443 EXPECT_V2_NEAR(decomposed_scale, scale, 0.00001f);
444 EXPECT_NEAR(decomposed_rotation.radian(), rotation.radian(), 0.00001f);
445}
446
447TEST(math_matrix, MatrixToQuaternionLegacy)
448{
449 float3x3 mat = {{0.808309, -0.578051, -0.111775},
450 {0.47251, 0.750174, -0.462572},
451 {0.351241, 0.321087, 0.879507}};
452
453 EXPECT_V4_NEAR(float4(to_quaternion_legacy(mat)),
454 float4(0.927091f, -0.211322f, 0.124857f, -0.283295f),
455 1e-5f);
456}
457
458TEST(math_matrix, MatrixTranspose)
459{
460 float4x4 m({1, 2, 3, 4}, {5, 6, 7, 8}, {9, 1, 2, 3}, {2, 5, 6, 7});
461 float4x4 expect({1, 5, 9, 2}, {2, 6, 1, 5}, {3, 7, 2, 6}, {4, 8, 3, 7});
462 EXPECT_EQ(transpose(m), expect);
463}
464
465TEST(math_matrix, MatrixInterpolationRegular)
466{
467 /* Test 4x4 matrix interpolation without singularity, i.e. without axis flip. */
468
469 /* Transposed matrix, so that the code here is written in the same way as print_m4() outputs. */
470 /* This matrix represents T=(0.1, 0.2, 0.3), R=(40, 50, 60) degrees, S=(0.7, 0.8, 0.9) */
471 float4x4 m2 = transpose(float4x4({0.224976f, -0.333770f, 0.765074f, 0.100000f},
472 {0.389669f, 0.647565f, 0.168130f, 0.200000f},
473 {-0.536231f, 0.330541f, 0.443163f, 0.300000f},
474 {0.000000f, 0.000000f, 0.000000f, 1.000000f}));
477 const float epsilon = 1e-6;
478 result = interpolate(m1, m2, 0.0f);
479 EXPECT_M4_NEAR(result, m1, epsilon);
480 result = interpolate(m1, m2, 1.0f);
481 EXPECT_M4_NEAR(result, m2, epsilon);
482
483 /* This matrix is based on the current implementation of the code, and isn't guaranteed to be
484 * correct. It's just consistent with the current implementation. */
485 float4x4 expect = transpose(float4x4({0.690643f, -0.253244f, 0.484996f, 0.050000f},
486 {0.271924f, 0.852623f, 0.012348f, 0.100000f},
487 {-0.414209f, 0.137484f, 0.816778f, 0.150000f},
488 {0.000000f, 0.000000f, 0.000000f, 1.000000f}));
489 result = interpolate(m1, m2, 0.5f);
490 EXPECT_M4_NEAR(result, expect, epsilon);
491
492 result = interpolate_fast(m1, m2, 0.5f);
493 EXPECT_M4_NEAR(result, expect, epsilon);
494}
495
496TEST(math_matrix, MatrixInterpolationSingularity)
497{
498 /* A singularity means that there is an axis mirror in the rotation component of the matrix.
499 * This is reflected in its negative determinant.
500 *
501 * The interpolation of 4x4 matrices performs linear interpolation on the translation component,
502 * and then uses the 3x3 interpolation function to handle rotation and scale. As a result, this
503 * test for a singularity in the rotation matrix only needs to test the 3x3 case. */
504
505 /* Transposed matrix, so that the code here is written in the same way as print_m4() outputs. */
506 /* This matrix represents R=(4, 5, 6) degrees, S=(-1, 1, 1) */
507 float3x3 matrix_a = transpose(float3x3({-0.990737f, -0.098227f, 0.093759f},
508 {-0.104131f, 0.992735f, -0.060286f},
509 {0.087156f, 0.069491f, 0.993768f}));
510 EXPECT_NEAR(-1.0f, determinant(matrix_a), 1e-6);
511
512 /* This matrix represents R=(0, 0, 0), S=(-1, 1 1) */
513 float3x3 matrix_b = transpose(
514 float3x3({-1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}));
515
516 float3x3 result = interpolate(matrix_a, matrix_b, 0.0f);
517 EXPECT_M3_NEAR(result, matrix_a, 1e-5);
518
519 result = interpolate(matrix_a, matrix_b, 1.0f);
520 EXPECT_M3_NEAR(result, matrix_b, 1e-5);
521
522 result = interpolate(matrix_a, matrix_b, 0.5f);
523
524 float3x3 expect = transpose(float3x3({-0.997681f, -0.049995f, 0.046186f},
525 {-0.051473f, 0.998181f, -0.031385f},
526 {0.044533f, 0.033689f, 0.998440f}));
527 EXPECT_M3_NEAR(result, expect, 1e-5);
528
529 result = interpolate_fast(matrix_a, matrix_b, 0.5f);
530 EXPECT_M3_NEAR(result, expect, 1e-5);
531
532 /* Interpolating between a matrix with and without axis flip can cause it to go through a zero
533 * point. The determinant det(A) of a matrix represents the change in volume; interpolating
534 * between matrices with det(A)=-1 and det(B)=1 will have to go through a point where
535 * det(result)=0, so where the volume becomes zero. */
536 float3x3 matrix_i = float3x3::identity();
537 expect = float3x3::zero();
538 result = interpolate(matrix_a, matrix_i, 0.5f);
539 EXPECT_NEAR(0.0f, determinant(result), 1e-5);
540 EXPECT_M3_NEAR(result, expect, 1e-5);
541}
542
543TEST(math_matrix, MatrixTransform)
544{
545 float3 expect, result;
546 const float3 p(1, 2, 3);
549 float4x4 pers4 = projection::perspective(-0.1f, 0.1f, -0.1f, 0.1f, -0.1f, -1.0f);
550 float3x3 pers3 = float3x3({1, 0, 0.1f}, {0, 1, 0.1f}, {0, 0.1f, 1});
551
552 expect = {13, 2, -1};
553 result = transform_point(m4, p);
554 EXPECT_V3_NEAR(result, expect, 1e-2);
555
556 expect = {3, 2, -1};
557 result = transform_point(m3, p);
558 EXPECT_V3_NEAR(result, expect, 1e-5);
559
561 EXPECT_V3_NEAR(result, expect, 1e-5);
562
564 EXPECT_V3_NEAR(result, expect, 1e-5);
565
566 expect = {-0.333333, -0.666666, -1.14814};
567 result = project_point(pers4, p);
568 EXPECT_V3_NEAR(result, expect, 1e-5);
569
570 float2 expect2 = {0.76923, 1.61538};
571 float2 result2 = project_point(pers3, float2(p));
572 EXPECT_V2_NEAR(result2, expect2, 1e-5);
573}
574
575TEST(math_matrix, MatrixTransform2D)
576{
577 const float2 sample_point = float2(2.0f, 3.0f);
578 const float3x3 transformation = math::from_loc_rot_scale<float3x3>(
579 float2(2.0f, 0.5f), AngleRadian(M_PI_2), float2(1.5f, 1.0f));
580
581 EXPECT_V2_NEAR(transform_point(transformation, sample_point), float2(-1.0f, 3.5f), 1e-3f);
582
583 const float2x2 transformation_2d = float2x2(transformation);
584 EXPECT_V2_NEAR(transform_point(transformation_2d, sample_point), float2(-3.0f, 3.0f), 1e-3f);
585}
586
587TEST(math_matrix, MatrixProjection)
588{
589 using namespace math::projection;
590 float4x4 expect;
591 float4x4 ortho = orthographic(-0.2f, 0.3f, -0.2f, 0.4f, -0.2f, -0.5f);
592 float4x4 pers1 = perspective(-0.2f, 0.3f, -0.2f, 0.4f, -0.2f, -0.5f);
593 float4x4 pers2 = perspective_fov(
594 math::atan(-0.2f), math::atan(0.3f), math::atan(-0.2f), math::atan(0.4f), -0.2f, -0.5f);
595
596 expect = transpose(float4x4({4.0f, 0.0f, 0.0f, -0.2f},
597 {0.0f, 3.33333f, 0.0f, -0.333333f},
598 {0.0f, 0.0f, 6.66667f, -2.33333f},
599 {0.0f, 0.0f, 0.0f, 1.0f}));
600 EXPECT_M4_NEAR(ortho, expect, 1e-5);
601
602 expect = transpose(float4x4({-0.8f, 0.0f, 0.2f, 0.0f},
603 {0.0f, -0.666667f, 0.333333f, 0.0f},
604 {0.0f, 0.0f, -2.33333f, 0.666667f},
605 {0.0f, 0.0f, -1.0f, 0.0f}));
606 EXPECT_M4_NEAR(pers1, expect, 1e-5);
607
608 expect = transpose(float4x4({4.0f, 0.0f, 0.2f, 0.0f},
609 {0.0f, 3.33333f, 0.333333f, 0.0f},
610 {0.0f, 0.0f, -2.33333f, 0.666667f},
611 {0.0f, 0.0f, -1.0f, 0.0f}));
612 EXPECT_M4_NEAR(pers2, expect, 1e-5);
613}
614
615TEST(math_matrix, ToQuaternionSafe)
616{
617 float3x3 mat;
618 mat[0] = {0.493316412f, -0.0f, 0.869849861f};
619 mat[1] = {-0.0f, 1.0f, 0.0f};
620 mat[2] = {-0.0176299568f, -0.0f, 0.999844611f};
621
622 float3x3 expect;
623 expect[0] = {0.493316f, 0.000000f, 0.869850f};
624 expect[1] = {-0.000000f, 1.000000f, 0.000000f};
625 expect[2] = {-0.869850f, -0.000000f, 0.493316f};
626
627 /* This is mainly testing if there are any asserts hit because the matrix has shearing. */
629 EXPECT_M3_NEAR(from_rotation<float3x3>(rotation), expect, 1e-5);
630}
631
633{
634 Array<float3, 10> transformed_normals(normals.size());
635 transform_normals(normals, transform, transformed_normals);
636 /* Input unit vectors should still be unit length. */
637 for (const int64_t i : transformed_normals.index_range()) {
638 EXPECT_NEAR(math::length(transformed_normals[i]), 1.0f, 1e-6f);
639 }
640 /* Make sure any optimization inside #transform_normals that skips the final vector normalization
641 * doesn't change the result. */
642 const float3x3 normal_transform = math::transpose(math::invert(transform));
643 for (const int64_t i : normals.index_range()) {
644 const float3 transformed_then_normalized = math::normalize(normal_transform * normals[i]);
645 EXPECT_V3_NEAR(transformed_normals[i], transformed_then_normalized, 1e-6f);
646 }
647}
648
649TEST(math_matrix, TransformNormals)
650{
653 for (const int64_t i : normals.index_range()) {
654 normals[i] = rng.get_unit_float3();
655 }
656 for (const int64_t i : normals.index_range()) {
657 EXPECT_NEAR(math::length(normals[i]), 1.0f, 1e-6f);
658 }
659
666 normals);
668 from_rot_scale<float3x3>(EulerXYZ(0.0f, 7.1f, 3.14f), float3(0.5f, 1.5f, 5.7f)), normals);
669}
670
671} // namespace blender::tests
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
#define M_SQRT2
#define M_PI_2
#define M_SQRT1_2
void interp_m4_m4m4(float R[4][4], const float A[4][4], const float B[4][4], float t)
void unit_m3(float m[3][3])
void zero_m3(float m[3][3])
#define mul_m4_series(...)
void interp_m3_m3m3(float R[3][3], const float A[3][3], const float B[3][3], float t)
float determinant_m3_array(const float m[3][3])
void transpose_m3(float R[3][3])
#define mul_m3_series(...)
void pseudoinverse_m4_m4(float inverse[4][4], const float mat[4][4], float epsilon)
void transpose_m4(float R[4][4])
void unit_m4(float m[4][4])
TEST(math_matrix, interp_m4_m4m4_regular)
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
SIMD_FORCE_INLINE btVector3 transform(const btVector3 &point) const
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
btMatrix3x3 adjoint() const
Return the adjoint of the matrix.
btScalar determinant() const
Return the determinant of the matrix.
IndexRange index_range() const
Definition BLI_array.hh:360
static float normals[][3]
VecBase< float, D > normalize(VecOp< float, D >) RET
#define abs
MatBase< R, C > transpose(MatBase< C, R >) RET
MatBase< T, 4, 4 > perspective(T left, T right, T bottom, T top, T near_clip, T far_clip)
Create a perspective projection matrix using OpenGL coordinate convention: Maps each axis range to [-...
AngleRadianBase< float > AngleRadian
QuaternionBase< float > Quaternion
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
QuaternionBase< T > to_quaternion(const AxisAngleBase< T, AngleT > &axis_angle)
bool is_negative(const MatBase< T, 3, 3 > &mat)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
bool is_orthonormal(const MatT &mat)
Quaternion to_quaternion_legacy(const float3x3 &mat)
MatT from_up_axis(const VectorT up)
T length(const VecBase< T, Size > &a)
MatT from_loc_rot(const typename MatT::loc_type &location, const RotationT &rotation)
AxisAngleBase< float, AngleRadianBase< float > > AxisAngle
bool is_uniformly_scaled(const MatT &mat)
bool is_unit_scale(const MatBase< T, NumCol, NumRow > &m)
MatBase< T, Size, Size > pseudo_invert(const MatBase< T, Size, Size > &mat, T epsilon=1e-8)
EulerXYZBase< float > EulerXYZ
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
T atan(const T &a)
bool is_zero(const T &a)
CartesianBasis invert(const CartesianBasis &basis)
DualQuaternionBase< float > DualQuaternion
VectorT project_point(const MatT &mat, const VectorT &point)
MatT from_scale(const VecBase< typename MatT::base_type, ScaleDim > &scale)
T interpolate(const T &a, const T &b, const FactorT &t)
void transform_normals(const float3x3 &transform, MutableSpan< float3 > normals)
EulerXYZBase< T > to_nearest_euler(const MatBase< T, 3, 3 > &mat, const EulerXYZBase< T > &reference)
MatBase< T, 3, 3 > interpolate_fast(const MatBase< T, 3, 3 > &a, const MatBase< T, 3, 3 > &b, T t)
MatBase< T, NumCol, NumRow > translate(const MatBase< T, NumCol, NumRow > &mat, const VectorT &translation)
QuaternionBase< T > normalized_to_quaternion_safe(const MatBase< T, 3, 3 > &mat)
MatBase< T, NumCol, NumRow > normalize_and_get_size(const MatBase< T, NumCol, NumRow > &a, VectorT &r_size)
AxisAngleBase< T, AngleT > to_axis_angle(const EulerXYZBase< T > &euler)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > to_scale(const MatBase< T, NumCol, NumRow > &mat)
bool is_equal(const MatBase< T, NumCol, NumRow > &a, const MatBase< T, NumCol, NumRow > &b, const T epsilon=T(0))
void to_rot_scale(const MatBase< T, 2, 2 > &mat, AngleRadianBase< T > &r_rotation, VecBase< T, 2 > &r_scale)
Euler3Base< float > Euler3
MatT from_location(const typename MatT::loc_type &location)
void to_loc_rot_scale(const MatBase< T, 3, 3 > &mat, VecBase< T, 2 > &r_location, AngleRadianBase< T > &r_rotation, VecBase< T, 2 > &r_scale)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
MatT from_rot_scale(const RotationT &rotation, const VectorT &scale)
MatBase< T, NumCol, NumRow > rotate(const MatBase< T, NumCol, NumRow > &mat, const RotationT &rotation)
MatT from_rotation(const RotationT &rotation)
Euler3Base< T > to_euler(const AxisAngleBase< T, AngleT > &axis_angle, EulerOrder order)
MatT from_loc_rot_scale(const typename MatT::loc_type &location, const RotationT &rotation, const VecBase< typename MatT::base_type, ScaleDim > &scale)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
bool is_orthogonal(const MatT &mat)
static void transform_normals_test(const float3x3 &transform, const Span< float3 > normals)
TEST(blf_load, load)
Definition BLF_tests.cc:34
MatBase< float, 2, 2 > float2x2
MatBase< float, 4, 4 > float4x4
MatBase< double, 3, 3 > double3x3
VecBase< float, 4 > float4
VecBase< float, 2 > float2
MatBase< double, 2, 2 > double2x2
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
MatBase< double, 4, 4 > double4x4
static MatBase diagonal(float value)
const c_style_mat & ptr() const
i
Definition text_draw.cc:230