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