This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/blenkernel/intern/armature_test.cc
Hallam Roberts 3da84d8b08 Cleanup: use M_PI_2 and M_PI_4 where possible
The constant M_PI_4 is added to GLSL to ensure it works there too.

Differential Revision: https://developer.blender.org/D14288
2022-03-11 18:27:58 +01:00

427 lines
15 KiB
C++

/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2020 Blender Foundation. All rights reserved. */
#include "BKE_armature.hh"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "DNA_armature_types.h"
#include "testing/testing.h"
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];
float roll;
unit_m3(unit_matrix);
/* Any vector with a unit matrix should return zero roll. */
mat3_vec_to_roll(unit_matrix, unit_matrix[0], &roll);
EXPECT_FLOAT_EQ(0.0f, roll);
mat3_vec_to_roll(unit_matrix, unit_matrix[1], &roll);
EXPECT_FLOAT_EQ(0.0f, roll);
mat3_vec_to_roll(unit_matrix, unit_matrix[2], &roll);
EXPECT_FLOAT_EQ(0.0f, roll);
{
/* Non-unit vector. */
float vector[3] = {1.0f, 1.0f, 1.0f};
mat3_vec_to_roll(unit_matrix, vector, &roll);
EXPECT_NEAR(0.0f, roll, FLOAT_EPSILON);
/* Normalized version of the above vector. */
normalize_v3(vector);
mat3_vec_to_roll(unit_matrix, vector, &roll);
EXPECT_NEAR(0.0f, roll, FLOAT_EPSILON);
}
}
TEST(mat3_vec_to_roll, Rotationmatrix)
{
float rotation_matrix[3][3];
float roll;
const float rot_around_x[3] = {1.234f, 0.0f, 0.0f};
eul_to_mat3(rotation_matrix, rot_around_x);
{
const float unit_axis_x[3] = {1.0f, 0.0f, 0.0f};
mat3_vec_to_roll(rotation_matrix, unit_axis_x, &roll);
EXPECT_NEAR(1.234f, roll, FLOAT_EPSILON);
}
{
const float unit_axis_y[3] = {0.0f, 1.0f, 0.0f};
mat3_vec_to_roll(rotation_matrix, unit_axis_y, &roll);
EXPECT_NEAR(0, roll, FLOAT_EPSILON);
}
{
const float unit_axis_z[3] = {0.0f, 0.0f, 1.0f};
mat3_vec_to_roll(rotation_matrix, unit_axis_z, &roll);
EXPECT_NEAR(0, roll, FLOAT_EPSILON);
}
{
const float between_x_and_y[3] = {1.0f, 1.0f, 0.0f};
mat3_vec_to_roll(rotation_matrix, between_x_and_y, &roll);
EXPECT_NEAR(0.57158958f, roll, FLOAT_EPSILON);
}
}
/** 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 input_normalized[3];
float roll_mat[3][3];
if (normalize) {
/* The vector is re-normalized 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.50e-4, 0.01e-4);
}
TEST(vec_roll_to_mat3_normalized, FlippedBoundary2)
{
EXPECT_NEAR(find_flip_boundary(1, 1), 2.50e-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 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);
}
TEST(vec_roll_to_mat3_normalized, Flipped2)
{
/* If normalized_vector is close to -Y and
* 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 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);
}
/* 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);
}
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);
}
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_2), expected_roll_mat);
}
/** Test that the matrix is orthogonal for an input close to -Y. */
static double test_vec_roll_to_mat3_orthogonal(double s, double x, double z)
{
const float input[3] = {float(x), float(s * sqrt(1 - x * x - z * z)), float(z)};
return test_vec_roll_to_mat3_normalized(input, 0.0f, nullptr);
}
/** Test that the matrix is orthogonal for a range of inputs close to -Y. */
static void test_vec_roll_to_mat3_orthogonal(double s, double x1, double x2, double y1, double y2)
{
const int count = 5000;
double delta = 0;
double tmax = 0;
for (int i = 0; i <= count; i++) {
double t = double(i) / count;
double det = test_vec_roll_to_mat3_orthogonal(s, interpd(x2, x1, t), interpd(y2, y1, t));
/* Find and report maximum error in the matrix determinant. */
double curdelta = abs(det - 1);
if (curdelta > delta) {
delta = curdelta;
tmax = t;
}
}
printf(" Max determinant deviation %.10f at %f.\n", delta, tmax);
}
#define TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(name, s, x1, x2, y1, y2) \
TEST(vec_roll_to_mat3_normalized, name) \
{ \
test_vec_roll_to_mat3_orthogonal(s, x1, x2, y1, y2); \
}
/* Moving from -Y towards X. */
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_005, -1, 0, 0, 3e-4, 0.005)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_010, -1, 0, 0, 0.005, 0.010)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_050, -1, 0, 0, 0.010, 0.050)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_100, -1, 0, 0, 0.050, 0.100)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_200, -1, 0, 0, 0.100, 0.200)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_000_300, -1, 0, 0, 0.200, 0.300)
/* Moving from -Y towards X and Y. */
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_005_005, -1, 3e-4, 0.005, 3e-4, 0.005)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_010_010, -1, 0.005, 0.010, 0.005, 0.010)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_050_050, -1, 0.010, 0.050, 0.010, 0.050)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_100_100, -1, 0.050, 0.100, 0.050, 0.100)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoN_200_200, -1, 0.100, 0.200, 0.100, 0.200)
/* Moving from +Y towards X. */
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_000_005, 1, 0, 0, 0, 0.005)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_000_100, 1, 0, 0, 0.005, 0.100)
/* Moving from +Y towards X and Y. */
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_005_005, 1, 0, 0.005, 0, 0.005)
TEST_VEC_ROLL_TO_MAT3_ORTHOGONAL(OrthoP_100_100, 1, 0.005, 0.100, 0.005, 0.100)
class BKE_armature_find_selected_bones_test : public testing::Test {
protected:
bArmature arm;
Bone bone1, bone2, bone3;
void SetUp() override
{
strcpy(bone1.name, "bone1");
strcpy(bone2.name, "bone2");
strcpy(bone3.name, "bone3");
arm.bonebase = {nullptr, nullptr};
bone1.childbase = {nullptr, nullptr};
bone2.childbase = {nullptr, nullptr};
bone3.childbase = {nullptr, nullptr};
BLI_addtail(&arm.bonebase, &bone1); /* bone1 is root bone. */
BLI_addtail(&arm.bonebase, &bone2); /* bone2 is root bone. */
BLI_addtail(&bone2.childbase, &bone3); /* bone3 has bone2 as parent. */
/* Make sure the armature & its bones are visible, to make them selectable. */
arm.layer = bone1.layer = bone2.layer = bone3.layer = 1;
}
};
TEST_F(BKE_armature_find_selected_bones_test, some_bones_selected)
{
bone1.flag = BONE_SELECTED;
bone2.flag = 0;
bone3.flag = BONE_SELECTED;
std::vector<Bone *> seen_bones;
auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback);
ASSERT_EQ(seen_bones.size(), 2) << "Expected 2 selected bones, got " << seen_bones.size();
EXPECT_EQ(seen_bones[0], &bone1);
EXPECT_EQ(seen_bones[1], &bone3);
EXPECT_FALSE(result.all_bones_selected); /* Bone 2 was not selected. */
EXPECT_FALSE(result.no_bones_selected); /* Bones 1 and 3 were selected. */
}
TEST_F(BKE_armature_find_selected_bones_test, no_bones_selected)
{
bone1.flag = bone2.flag = bone3.flag = 0;
std::vector<Bone *> seen_bones;
auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback);
EXPECT_TRUE(seen_bones.empty()) << "Expected no selected bones, got " << seen_bones.size();
EXPECT_FALSE(result.all_bones_selected);
EXPECT_TRUE(result.no_bones_selected);
}
TEST_F(BKE_armature_find_selected_bones_test, all_bones_selected)
{
bone1.flag = bone2.flag = bone3.flag = BONE_SELECTED;
std::vector<Bone *> seen_bones;
auto callback = [&](Bone *bone) { seen_bones.push_back(bone); };
SelectedBonesResult result = BKE_armature_find_selected_bones(&arm, callback);
ASSERT_EQ(seen_bones.size(), 3) << "Expected 3 selected bones, got " << seen_bones.size();
EXPECT_EQ(seen_bones[0], &bone1);
EXPECT_EQ(seen_bones[1], &bone2);
EXPECT_EQ(seen_bones[2], &bone3);
EXPECT_TRUE(result.all_bones_selected);
EXPECT_FALSE(result.no_bones_selected);
}
} // namespace blender::bke::tests