Split and extend unit tests for vec_roll_to_mat3_normalized.

Separate the huge test into huge logical parts and add more cases
to check. Also add a utility to check that the matrix is orthogonal,
with arbitrary epsilon values and calculations in double.

A couple of tests deliberately fail, to be fixed in following commits.

Ref D9551
This commit is contained in:
Alexander Gavrilov 2020-11-21 16:06:02 +03:00
parent 6cd191a660
commit dfa1c7e554
3 changed files with 222 additions and 54 deletions

View File

@ -30,6 +30,36 @@ namespace blender::bke::tests {
static const float FLOAT_EPSILON = 1.2e-7;
static const float SCALE_EPSILON = 3.71e-5;
static const float ORTHO_EPSILON = 5e-5;
/** Test that the matrix is orthogonal, i.e. has no scale or shear within acceptable precision. */
static double EXPECT_M3_ORTHOGONAL(const float mat[3][3],
double epsilon_scale,
double epsilon_ortho)
{
/* Do the checks in double precision to avoid precision issues in the checks themselves. */
double dmat[3][3];
copy_m3d_m3(dmat, mat);
/* Check individual axis scaling. */
EXPECT_NEAR(len_v3_db(dmat[0]), 1.0, epsilon_scale);
EXPECT_NEAR(len_v3_db(dmat[1]), 1.0, epsilon_scale);
EXPECT_NEAR(len_v3_db(dmat[2]), 1.0, epsilon_scale);
/* Check orthogonality. */
EXPECT_NEAR(dot_v3v3_db(dmat[0], dmat[1]), 0.0, epsilon_ortho);
EXPECT_NEAR(dot_v3v3_db(dmat[0], dmat[2]), 0.0, epsilon_ortho);
EXPECT_NEAR(dot_v3v3_db(dmat[1], dmat[2]), 0.0, epsilon_ortho);
/* Check determinant to detect flipping and as a secondary volume change check. */
double determinant = determinant_m3_array_db(dmat);
EXPECT_NEAR(determinant, 1.0, epsilon_ortho);
return determinant;
}
TEST(mat3_vec_to_roll, UnitMatrix)
{
float unit_matrix[3][3];
@ -93,71 +123,185 @@ TEST(mat3_vec_to_roll, Rotationmatrix)
}
}
TEST(vec_roll_to_mat3_normalized, Rotationmatrix)
/** Generic function to test vec_roll_to_mat3_normalized. */
static double test_vec_roll_to_mat3_normalized(const float input[3],
float roll,
const float expected_roll_mat[3][3],
bool normalize = true)
{
float negative_y_axis[3][3];
unit_m3(negative_y_axis);
negative_y_axis[0][0] = negative_y_axis[1][1] = -1.0f;
const float roll = 0.0f;
float input_normalized[3];
float roll_mat[3][3];
if (normalize) {
/* The vector is renormalized to replicate the actual usage. */
normalize_v3_v3(input_normalized, input);
}
else {
copy_v3_v3(input_normalized, input);
}
vec_roll_to_mat3_normalized(input_normalized, roll, roll_mat);
EXPECT_V3_NEAR(roll_mat[1], input_normalized, FLT_EPSILON);
if (expected_roll_mat) {
EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
}
return EXPECT_M3_ORTHOGONAL(roll_mat, SCALE_EPSILON, ORTHO_EPSILON);
}
/** Binary search to test where the code switches to the most degenerate special case. */
static double find_flip_boundary(double x, double z)
{
/* Irrational scale factor to ensure values aren't 'nice', have a lot of rounding errors,
* and can't accidentally produce the exact result returned by the special case. */
const double scale = M_1_PI / 10;
double theta = x * x + z * z;
double minv = 0, maxv = 1e-2;
while (maxv - minv > FLT_EPSILON * 1e-3) {
double mid = (minv + maxv) / 2;
float roll_mat[3][3];
float input[3] = {float(x * mid * scale),
-float(sqrt(1 - theta * mid * mid) * scale),
float(z * mid * scale)};
normalize_v3(input);
vec_roll_to_mat3_normalized(input, 0, roll_mat);
/* The special case assigns exact constants rather than computing. */
if (roll_mat[0][0] == -1 && roll_mat[0][1] == 0 && roll_mat[2][1] == 0) {
minv = mid;
}
else {
maxv = mid;
}
}
return sqrt(theta) * (minv + maxv) * 0.5;
}
TEST(vec_roll_to_mat3_normalized, FlippedBoundary1)
{
EXPECT_NEAR(find_flip_boundary(0, 1), 2.40e-4, 0.01e-4);
}
TEST(vec_roll_to_mat3_normalized, FlippedBoundary2)
{
EXPECT_NEAR(find_flip_boundary(1, 1), 3.39e-4, 0.01e-4);
}
/* Test cases close to the -Y axis. */
TEST(vec_roll_to_mat3_normalized, Flipped1)
{
/* If normalized_vector is -Y, simple symmetry by Z axis. */
{
const float normalized_vector[3] = {0.0f, -1.0f, 0.0f};
vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
EXPECT_M3_NEAR(roll_mat, negative_y_axis, FLT_EPSILON);
}
const float input[3] = {0.0f, -1.0f, 0.0f};
const float expected_roll_mat[3][3] = {
{-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
}
/* If normalized_vector is far enough from -Y, apply the general case. */
{
const float expected_roll_mat[3][3] = {{1.000000f, 0.000000f, 0.000000f},
{0.000000f, -0.999989986f, -0.000000f},
{0.000000f, 0.000000f, 1.000000f}};
const float normalized_vector[3] = {0.0f, -1.0f + 1e-5f, 0.0f};
vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
}
#if 0
/* TODO: This test will pass after fixing T82455) */
TEST(vec_roll_to_mat3_normalized, Flipped2)
{
/* If normalized_vector is close to -Y and
* it has X and Z values above a threshold,
* apply the special case. */
{
const float expected_roll_mat[3][3] = {{0.000000f, -9.99999975e-06f, 1.000000f},
{9.99999975e-06f, -0.999999881f, 9.99999975e-06f},
{1.000000f, -9.99999975e-06, 0.000000f}};
const float normalized_vector[3] = {1e-24, -0.999999881, 0};
vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
}
#endif
* it has X and Z values below a threshold,
* simple symmetry by Z axis. */
const float input[3] = {1e-24, -0.999999881, 0};
const float expected_roll_mat[3][3] = {
{-1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
}
TEST(vec_roll_to_mat3_normalized, Flipped3)
{
/* If normalized_vector is in a critical range close to -Y, apply the special case. */
{
const float expected_roll_mat[3][3] = {{0.000000f, -9.99999975e-06f, 1.000000f},
{9.99999975e-06f, -0.999999881f, 9.99999975e-06f},
{1.000000f, -9.99999975e-06f, 0.000000f}};
const float input[3] = {2.5e-4f, -0.999999881f, 2.5e-4f}; /* Corner Case. */
const float expected_roll_mat[3][3] = {{0.000000f, -2.5e-4f, 1.000000f},
{2.5e-4f, -0.999999881f, 2.5e-4f},
{1.000000f, -2.5e-4f, 0.000000f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat, false);
}
const float normalized_vector[3] = {1e-5f, -0.999999881f, 1e-5f}; /* Corner Case. */
vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
}
/* Test 90 degree rotations. */
TEST(vec_roll_to_mat3_normalized, Rotate90_Z_CW)
{
/* Rotate 90 around Z. */
const float input[3] = {1, 0, 0};
const float expected_roll_mat[3][3] = {{0, -1, 0}, {1, 0, 0}, {0, 0, 1}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
/* If normalized_vector is far enough from -Y, apply the general case. */
{
const float expected_roll_mat[3][3] = {{0.788675129f, -0.577350259f, -0.211324856f},
{0.577350259f, 0.577350259f, 0.577350259f},
{-0.211324856f, -0.577350259f, 0.788675129f}};
TEST(vec_roll_to_mat3_normalized, Rotate90_Z_CCW)
{
/* Rotate 90 around Z. */
const float input[3] = {-1, 0, 0};
const float expected_roll_mat[3][3] = {{0, 1, 0}, {-1, 0, 0}, {0, 0, 1}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
const float vector[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
float normalized_vector[3];
normalize_v3_v3(normalized_vector, vector);
vec_roll_to_mat3_normalized(normalized_vector, roll, roll_mat);
EXPECT_M3_NEAR(roll_mat, expected_roll_mat, FLT_EPSILON);
}
TEST(vec_roll_to_mat3_normalized, Rotate90_X_CW)
{
/* Rotate 90 around X. */
const float input[3] = {0, 0, -1};
const float expected_roll_mat[3][3] = {{1, 0, 0}, {0, 0, -1}, {0, 1, 0}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
TEST(vec_roll_to_mat3_normalized, Rotate90_X_CCW)
{
/* Rotate 90 around X. */
const float input[3] = {0, 0, 1};
const float expected_roll_mat[3][3] = {{1, 0, 0}, {0, 0, 1}, {0, -1, 0}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
/* Test the general case when the vector is far enough from -Y. */
TEST(vec_roll_to_mat3_normalized, Generic1)
{
const float input[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
const float expected_roll_mat[3][3] = {{0.788675129f, -0.577350259f, -0.211324856f},
{0.577350259f, 0.577350259f, 0.577350259f},
{-0.211324856f, -0.577350259f, 0.788675129f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
TEST(vec_roll_to_mat3_normalized, Generic2)
{
const float input[3] = {1.0f, -1.0f, 1.0f}; /* Arbitrary Value. */
const float expected_roll_mat[3][3] = {{0.211324856f, -0.577350259f, -0.788675129f},
{0.577350259f, -0.577350259f, 0.577350259f},
{-0.788675129f, -0.577350259f, 0.211324856f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
TEST(vec_roll_to_mat3_normalized, Generic3)
{
const float input[3] = {-1.0f, -1.0f, 1.0f}; /* Arbitrary Value. */
const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, 0.788675129f},
{-0.577350259f, -0.577350259f, 0.577350259f},
{0.788675129f, -0.577350259f, 0.211324856f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
TEST(vec_roll_to_mat3_normalized, Generic4)
{
const float input[3] = {-1.0f, -1.0f, -1.0f}; /* Arbitrary Value. */
const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, -0.788675129f},
{-0.577350259f, -0.577350259f, -0.577350259f},
{-0.788675129f, 0.577350259f, 0.211324856f}};
test_vec_roll_to_mat3_normalized(input, 0.0f, expected_roll_mat);
}
/* Test roll. */
TEST(vec_roll_to_mat3_normalized, Roll1)
{
const float input[3] = {1.0f, 1.0f, 1.0f}; /* Arbitrary Value. */
const float expected_roll_mat[3][3] = {{0.211324856f, 0.577350259f, -0.788675129f},
{0.577350259f, 0.577350259f, 0.577350259f},
{0.788675129f, -0.577350259f, -0.211324856f}};
test_vec_roll_to_mat3_normalized(input, float(M_PI * 0.5), expected_roll_mat);
}
class BKE_armature_find_selected_bones_test : public testing::Test {

View File

@ -57,6 +57,7 @@ void copy_m4_m4_db(double m1[4][4], const double m2[4][4]);
void copy_m3_m3d(float m1[3][3], const double m2[3][3]);
/* float->double */
void copy_m3d_m3(double m1[3][3], const float m2[3][3]);
void copy_m4d_m4(double m1[4][4], const float m2[4][4]);
void swap_m3m3(float m1[3][3], float m2[3][3]);
@ -291,6 +292,7 @@ float determinant_m3(
float a1, float a2, float a3, float b1, float b2, float b3, float c1, float c2, float c3);
float determinant_m3_array(const float m[3][3]);
float determinant_m4_mat3_array(const float m[4][4]);
double determinant_m3_array_db(const double m[3][3]);
float determinant_m4(const float m[4][4]);
#define PSEUDOINVERSE_EPSILON 1e-8f

View File

@ -180,6 +180,21 @@ void copy_m4_m2(float m1[4][4], const float m2[2][2])
m1[3][3] = 1.0f;
}
void copy_m3d_m3(double m1[3][3], const float m2[3][3])
{
m1[0][0] = m2[0][0];
m1[0][1] = m2[0][1];
m1[0][2] = m2[0][2];
m1[1][0] = m2[1][0];
m1[1][1] = m2[1][1];
m1[1][2] = m2[1][2];
m1[2][0] = m2[2][0];
m1[2][1] = m2[2][1];
m1[2][2] = m2[2][2];
}
void copy_m4d_m4(double m1[4][4], const float m2[4][4])
{
m1[0][0] = m2[0][0];
@ -1113,6 +1128,13 @@ float determinant_m4_mat3_array(const float m[4][4])
m[2][0] * (m[0][1] * m[1][2] - m[0][2] * m[1][1]));
}
double determinant_m3_array_db(const double m[3][3])
{
return (m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) -
m[1][0] * (m[0][1] * m[2][2] - m[0][2] * m[2][1]) +
m[2][0] * (m[0][1] * m[1][2] - m[0][2] * m[1][1]));
}
bool invert_m3_ex(float m[3][3], const float epsilon)
{
float tmp[3][3];