Blender V4.3
armature_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2020 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "BKE_armature.hh"
6
7#include "BLI_listbase.h"
8#include "BLI_math_matrix.h"
9#include "BLI_math_rotation.h"
10#include "BLI_math_vector.h"
11#include "BLI_string.h"
12
13#include "DNA_armature_types.h"
14
16
17#include "testing/testing.h"
18
19namespace blender::bke::tests {
20
21static const float FLOAT_EPSILON = 1.2e-7;
22
23static const float SCALE_EPSILON = 3.71e-5;
24static const float ORTHO_EPSILON = 5e-5;
25
27static double EXPECT_M3_ORTHOGONAL(const float mat[3][3],
28 double epsilon_scale,
29 double epsilon_ortho)
30{
31 /* Do the checks in double precision to avoid precision issues in the checks themselves. */
32 double dmat[3][3];
33 copy_m3d_m3(dmat, mat);
34
35 /* Check individual axis scaling. */
36 EXPECT_NEAR(len_v3_db(dmat[0]), 1.0, epsilon_scale);
37 EXPECT_NEAR(len_v3_db(dmat[1]), 1.0, epsilon_scale);
38 EXPECT_NEAR(len_v3_db(dmat[2]), 1.0, epsilon_scale);
39
40 /* Check orthogonality. */
41 EXPECT_NEAR(dot_v3v3_db(dmat[0], dmat[1]), 0.0, epsilon_ortho);
42 EXPECT_NEAR(dot_v3v3_db(dmat[0], dmat[2]), 0.0, epsilon_ortho);
43 EXPECT_NEAR(dot_v3v3_db(dmat[1], dmat[2]), 0.0, epsilon_ortho);
44
45 /* Check determinant to detect flipping and as a secondary volume change check. */
47
48 EXPECT_NEAR(determinant, 1.0, epsilon_ortho);
49
50 return determinant;
51}
52
54{
55 float unit_matrix[3][3];
56 float roll;
57
58 unit_m3(unit_matrix);
59
60 /* Any vector with a unit matrix should return zero roll. */
61 mat3_vec_to_roll(unit_matrix, unit_matrix[0], &roll);
62 EXPECT_FLOAT_EQ(0.0f, roll);
63
64 mat3_vec_to_roll(unit_matrix, unit_matrix[1], &roll);
65 EXPECT_FLOAT_EQ(0.0f, roll);
66
67 mat3_vec_to_roll(unit_matrix, unit_matrix[2], &roll);
68 EXPECT_FLOAT_EQ(0.0f, roll);
69
70 {
71 /* Non-unit vector. */
72 float vector[3] = {1.0f, 1.0f, 1.0f};
73 mat3_vec_to_roll(unit_matrix, vector, &roll);
74 EXPECT_NEAR(0.0f, roll, FLOAT_EPSILON);
75
76 /* Normalized version of the above vector. */
78 mat3_vec_to_roll(unit_matrix, vector, &roll);
79 EXPECT_NEAR(0.0f, roll, FLOAT_EPSILON);
80 }
81}
82
83TEST(mat3_vec_to_roll, Rotationmatrix)
84{
85 float rotation_matrix[3][3];
86 float roll;
87
88 const float rot_around_x[3] = {1.234f, 0.0f, 0.0f};
89 eul_to_mat3(rotation_matrix, rot_around_x);
90
91 {
92 const float unit_axis_x[3] = {1.0f, 0.0f, 0.0f};
93 mat3_vec_to_roll(rotation_matrix, unit_axis_x, &roll);
94 EXPECT_NEAR(1.234f, roll, FLOAT_EPSILON);
95 }
96
97 {
98 const float unit_axis_y[3] = {0.0f, 1.0f, 0.0f};
99 mat3_vec_to_roll(rotation_matrix, unit_axis_y, &roll);
100 EXPECT_NEAR(0, roll, FLOAT_EPSILON);
101 }
102
103 {
104 const float unit_axis_z[3] = {0.0f, 0.0f, 1.0f};
105 mat3_vec_to_roll(rotation_matrix, unit_axis_z, &roll);
106 EXPECT_NEAR(0, roll, FLOAT_EPSILON);
107 }
108
109 {
110 const float between_x_and_y[3] = {1.0f, 1.0f, 0.0f};
111 mat3_vec_to_roll(rotation_matrix, between_x_and_y, &roll);
112 EXPECT_NEAR(0.57158958f, roll, FLOAT_EPSILON);
113 }
114}
115
117static double test_vec_roll_to_mat3_normalized(const float input[3],
118 float roll,
119 const float expected_roll_mat[3][3],
120 bool normalize = true)
121{
122 float input_normalized[3];
123 float roll_mat[3][3];
124
125 if (normalize) {
126 /* The vector is re-normalized to replicate the actual usage. */
127 normalize_v3_v3(input_normalized, input);
128 }
129 else {
130 copy_v3_v3(input_normalized, input);
131 }
132
133 vec_roll_to_mat3_normalized(input_normalized, roll, roll_mat);
134
135 EXPECT_V3_NEAR(roll_mat[1], input_normalized, FLT_EPSILON);
136
137 if (expected_roll_mat) {
138 EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
139 }
140
142}
143
145static double find_flip_boundary(double x, double z)
146{
147 /* Irrational scale factor to ensure values aren't 'nice', have a lot of rounding errors,
148 * and can't accidentally produce the exact result returned by the special case. */
149 const double scale = M_1_PI / 10;
150 double theta = x * x + z * z;
151 double minv = 0, maxv = 1e-2;
152
153 while (maxv - minv > FLT_EPSILON * 1e-3) {
154 double mid = (minv + maxv) / 2;
155
156 float roll_mat[3][3];
157 float input[3] = {float(x * mid * scale),
158 -float(sqrt(1 - theta * mid * mid) * scale),
159 float(z * mid * scale)};
160
161 normalize_v3(input);
162 vec_roll_to_mat3_normalized(input, 0, roll_mat);
163
164 /* The special case assigns exact constants rather than computing. */
165 if (roll_mat[0][0] == -1 && roll_mat[0][1] == 0 && roll_mat[2][1] == 0) {
166 minv = mid;
167 }
168 else {
169 maxv = mid;
170 }
171 }
172
173 return sqrt(theta) * (minv + maxv) * 0.5;
174}
175
177{
178 EXPECT_NEAR(find_flip_boundary(0, 1), 2.50e-4, 0.01e-4);
179}
180
182{
183 EXPECT_NEAR(find_flip_boundary(1, 1), 2.50e-4, 0.01e-4);
184}
185
186/* Test cases close to the -Y axis. */
188{
189 /* If normalized_vector is -Y, simple symmetry by Z axis. */
190 const float input[3] = {0.0f, -1.0f, 0.0f};
191 const float expected_roll_mat[3][3] = {
192 {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
193 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
194}
195
197{
198 /* If normalized_vector is close to -Y and
199 * it has X and Z values below a threshold,
200 * simple symmetry by Z axis. */
201 const float input[3] = {1e-24, -0.999999881, 0};
202 const float expected_roll_mat[3][3] = {
203 {-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
204 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
205}
206
208{
209 /* If normalized_vector is in a critical range close to -Y, apply the special case. */
210 const float input[3] = {2.5e-4f, -0.999999881f, 2.5e-4f}; /* Corner Case. */
211 const float expected_roll_mat[3][3] = {{0.000000f, -2.5e-4f, -1.000000f},
212 {2.5e-4f, -0.999999881f, 2.5e-4f},
213 {-1.000000f, -2.5e-4f, 0.000000f}};
214 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
215}
216
217/* Test 90 degree rotations. */
219{
220 /* Rotate 90 around Z. */
221 const float input[3] = {1, 0, 0};
222 const float expected_roll_mat[3][3] = {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}};
223 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
224}
225
227{
228 /* Rotate 90 around Z. */
229 const float input[3] = {-1, 0, 0};
230 const float expected_roll_mat[3][3] = {{0, 1, 0}, {-1, 0, 0}, {0, 0, 1}};
231 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
232}
233
235{
236 /* Rotate 90 around X. */
237 const float input[3] = {0, 0, -1};
238 const float expected_roll_mat[3][3] = {{1, 0, 0}, {0, 0, -1}, {0, 1, 0}};
239 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
240}
241
243{
244 /* Rotate 90 around X. */
245 const float input[3] = {0, 0, 1};
246 const float expected_roll_mat[3][3] = {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}};
247 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
248}
249
250/* Test the general case when the vector is far enough from -Y. */
252{
253 const float input[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
254 const float expected_roll_mat[3][3] = {{0.788675129f, -0.577350259f, -0.211324856f},
255 {0.577350259f, 0.577350259f, 0.577350259f},
256 {-0.211324856f, -0.577350259f, 0.788675129f}};
257 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
258}
259
261{
262 const float input[3] = {1.0f, -1.0f, 1.0f}; /* Arbitrary Value. */
263 const float expected_roll_mat[3][3] = {{0.211324856f, -0.577350259f, -0.788675129f},
264 {0.577350259f, -0.577350259f, 0.577350259f},
265 {-0.788675129f, -0.577350259f, 0.211324856f}};
266 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
267}
268
270{
271 const float input[3] = {-1.0f, -1.0f, 1.0f}; /* Arbitrary Value. */
272 const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, 0.788675129f},
273 {-0.577350259f, -0.577350259f, 0.577350259f},
274 {0.788675129f, -0.577350259f, 0.211324856f}};
275 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
276}
277
279{
280 const float input[3] = {-1.0f, -1.0f, -1.0f}; /* Arbitrary Value. */
281 const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, -0.788675129f},
282 {-0.577350259f, -0.577350259f, -0.577350259f},
283 {-0.788675129f, 0.577350259f, 0.211324856f}};
284 test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
285}
286
287/* Test roll. */
289{
290 const float input[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
291 const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, -0.788675129f},
292 {0.577350259f, 0.577350259f, 0.577350259f},
293 {0.788675129f, -0.577350259f, -0.211324856f}};
294 test_vec_roll_to_mat3_normalized(input, float(M_PI_2), expected_roll_mat);
295}
296
298static double test_vec_roll_to_mat3_orthogonal(double s, double x, double z)
299{
300 const float input[3] = {float(x), float(s * sqrt(1 - x * x - z * z)), float(z)};
301
302 return test_vec_roll_to_mat3_normalized(input, 0.0f, nullptr);
303}
304
306static void test_vec_roll_to_mat3_orthogonal(double s, double x1, double x2, double y1, double y2)
307{
308 const int count = 5000;
309 double delta = 0;
310 double tmax = 0;
311
312 for (int i = 0; i <= count; i++) {
313 double t = double(i) / count;
314 double det = test_vec_roll_to_mat3_orthogonal(s, interpd(x2, x1, t), interpd(y2, y1, t));
315
316 /* Find and report maximum error in the matrix determinant. */
317 double curdelta = abs(det - 1);
318 if (curdelta > delta) {
319 delta = curdelta;
320 tmax = t;
321 }
322 }
323
324 printf(" Max determinant deviation %.10f at %f.\n", delta, tmax);
325}
326
327#define TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(name, s, x1, x2, y1, y2) \
328 TEST(vec_roll_to_mat3_normalized, name) \
329 { \
330 test_vec_roll_to_mat3_orthogonal(s, x1, x2, y1, y2); \
331 }
332
333/* Moving from -Y towards X. */
334TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_005, -1, 0, 0, 3e-4, 0.005)
335TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_010, -1, 0, 0, 0.005, 0.010)
336TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_050, -1, 0, 0, 0.010, 0.050)
337TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_100, -1, 0, 0, 0.050, 0.100)
338TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_200, -1, 0, 0, 0.100, 0.200)
339TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_300, -1, 0, 0, 0.200, 0.300)
340
341/* Moving from -Y towards X and Y. */
342TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_005_005, -1, 3e-4, 0.005, 3e-4, 0.005)
343TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_010_010, -1, 0.005, 0.010, 0.005, 0.010)
344TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_050_050, -1, 0.010, 0.050, 0.010, 0.050)
345TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_100_100, -1, 0.050, 0.100, 0.050, 0.100)
346TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_200_200, -1, 0.100, 0.200, 0.100, 0.200)
347
348/* Moving from +Y towards X. */
349TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_000_005, 1, 0, 0, 0, 0.005)
350TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_000_100, 1, 0, 0, 0.005, 0.100)
351
352/* Moving from +Y towards X and Y. */
353TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_005_005, 1, 0, 0.005, 0, 0.005)
354TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_100_100, 1, 0.005, 0.100, 0.005, 0.100)
355
356class BKE_armature_find_selected_bones_test : public testing::Test {
357 protected:
359 Bone bone1, bone2, bone3;
360
361 void SetUp() override
362 {
363 memset(&arm, 0, sizeof(arm));
364 memset(&bone1, 0, sizeof(Bone));
365 memset(&bone2, 0, sizeof(Bone));
366 memset(&bone3, 0, sizeof(Bone));
367
368 STRNCPY(bone1.name, "bone1");
369 STRNCPY(bone2.name, "bone2");
370 STRNCPY(bone3.name, "bone3");
371
372 BLI_addtail(&arm.bonebase, &bone1); /* bone1 is root bone. */
373 BLI_addtail(&arm.bonebase, &bone2); /* bone2 is root bone. */
374 BLI_addtail(&bone2.childbase, &bone3); /* bone3 has bone2 as parent. */
375 }
376};
377
379{
380 bone1.flag = BONE_SELECTED;
381 bone2.flag = 0;
382 bone3.flag = BONE_SELECTED;
383
384 std::vector<Bone *> seen_bones;
385 auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
386
388
389 ASSERT_EQ(seen_bones.size(), 2) << "Expected 2 selected bones, got " << seen_bones.size();
390 EXPECT_EQ(seen_bones[0], &bone1);
391 EXPECT_EQ(seen_bones[1], &bone3);
392
393 EXPECT_FALSE(result.all_bones_selected); /* Bone 2 was not selected. */
394 EXPECT_FALSE(result.no_bones_selected); /* Bones 1 and 3 were selected. */
395}
396
398{
399 bone1.flag = bone2.flag = bone3.flag = 0;
400
401 std::vector<Bone *> seen_bones;
402 auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
403
405
406 EXPECT_TRUE(seen_bones.empty()) << "Expected no selected bones, got " << seen_bones.size();
407 EXPECT_FALSE(result.all_bones_selected);
408 EXPECT_TRUE(result.no_bones_selected);
409}
410
412{
413 bone1.flag = bone2.flag = bone3.flag = BONE_SELECTED;
414
415 std::vector<Bone *> seen_bones;
416 auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
417
419
420 ASSERT_EQ(seen_bones.size(), 3) << "Expected 3 selected bones, got " << seen_bones.size();
421 EXPECT_EQ(seen_bones[0], &bone1);
422 EXPECT_EQ(seen_bones[1], &bone2);
423 EXPECT_EQ(seen_bones[2], &bone3);
424
425 EXPECT_TRUE(result.all_bones_selected);
426 EXPECT_FALSE(result.no_bones_selected);
427}
428
429} // namespace blender::bke::tests
C++ functions to deal with Armature collections (i.e. the successor of bone layers).
void mat3_vec_to_roll(const float mat[3][3], const float vec[3], float *r_roll)
Definition armature.cc:2467
void vec_roll_to_mat3_normalized(const float nor[3], float roll, float r_mat[3][3])
Definition armature.cc:2482
sqrt(x)+1/max(0
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
void BLI_addtail(struct ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:110
#define M_1_PI
#define M_PI_2
MINLINE double interpd(double target, double origin, double t)
void unit_m3(float m[3][3])
double determinant_m3_array_db(const double m[3][3])
void copy_m3d_m3(double m1[3][3], const float m2[3][3])
void eul_to_mat3(float mat[3][3], const float eul[3])
MINLINE double len_v3_db(const double a[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE double dot_v3v3_db(const double a[3], const double b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v3_v3(float r[3], const float a[3])
MINLINE float normalize_v3(float n[3])
#define STRNCPY(dst, src)
Definition BLI_string.h:593
typedef double(DMatrix)[4][4]
@ BONE_SELECTED
#define TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(name, s, x1, x2, y1, y2)
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
btScalar determinant() const
Return the determinant of the matrix.
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
SIMD_FORCE_INLINE btVector3 & normalize()
Normalize this vector x^2 + y^2 + z^2 = 1.
Definition btVector3.h:303
#define printf
DEGForeachIDComponentCallback callback
draw_view in_light_buf[] float
int count
static double find_flip_boundary(double x, double z)
static const float SCALE_EPSILON
TEST_F(BKE_armature_find_selected_bones_test, some_bones_selected)
static double test_vec_roll_to_mat3_orthogonal(double s, double x, double z)
static double EXPECT_M3_ORTHOGONAL(const float mat[3][3], double epsilon_scale, double epsilon_ortho)
static const float ORTHO_EPSILON
TEST(action_groups, ReconstructGroupsWithReordering)
static const float FLOAT_EPSILON
static double test_vec_roll_to_mat3_normalized(const float input[3], float roll, const float expected_roll_mat[3][3], bool normalize=true)
SelectedBonesResult BKE_armature_find_selected_bones(const bArmature *armature, SelectedBoneCallback callback)
char name[64]
ListBase childbase
ccl_device_inline int abs(int x)
Definition util/math.h:120