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