Files
test2/source/blender/animrig/intern/evaluation_test.cc
luz paz f29382bd7b Cleanup: Fix typos in source/blender/animrig
Fixes user-facing and non-user-facing typos.

Pull Request: https://projects.blender.org/blender/blender/pulls/146098
2025-09-12 07:19:27 +02:00

323 lines
12 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ANIM_action.hh"
#include "ANIM_evaluation.hh"
#include "evaluation_internal.hh"
#include "BKE_action.hh"
#include "BKE_animsys.h"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "DNA_object_types.h"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "BLI_math_base.h"
#include <optional>
#include "CLG_log.h"
#include "testing/testing.h"
namespace blender::animrig::tests {
using namespace blender::animrig::internal;
class AnimationEvaluationTest : public testing::Test {
protected:
Main *bmain;
Action *action;
Object *cube;
Slot *slot;
Layer *layer;
KeyframeSettings settings = get_keyframe_settings(false);
AnimationEvalContext anim_eval_context = {};
PointerRNA cube_rna_ptr;
public:
static void SetUpTestSuite()
{
/* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
CLG_init();
/* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
BKE_idtype_init();
}
static void TearDownTestSuite()
{
CLG_exit();
}
void SetUp() override
{
bmain = BKE_main_new();
action = BKE_id_new<Action>(bmain, "ACÄnimåtië");
cube = BKE_object_add_only_object(bmain, OB_EMPTY, "Küüübus");
slot = &action->slot_add();
ASSERT_EQ(assign_action_and_slot(action, slot, cube->id), ActionSlotAssignmentResult::OK);
layer = &action->layer_add("Kübus layer");
/* Make it easier to predict test values. */
settings.interpolation = BEZT_IPO_LIN;
cube_rna_ptr = RNA_pointer_create_discrete(&cube->id, &RNA_Object, &cube->id);
}
void TearDown() override
{
BKE_main_free(bmain);
}
/** Evaluate the layer, and return result for the given property. */
std::optional<float> evaluate_single_property(const StringRefNull rna_path,
const int array_index,
const float eval_time)
{
anim_eval_context.eval_time = eval_time;
EvaluationResult result = evaluate_layer(
cube_rna_ptr, *action, *layer, slot->handle, anim_eval_context);
const AnimatedProperty *loc0_result = result.lookup_ptr(PropIdentifier(rna_path, array_index));
if (!loc0_result) {
return {};
}
return loc0_result->value;
}
/** Evaluate the layer, and test that the given property evaluates to the expected value. */
testing::AssertionResult test_evaluate_layer(const StringRefNull rna_path,
const int array_index,
const float2 eval_time__expect_value)
{
const float eval_time = eval_time__expect_value[0];
const float expect_value = eval_time__expect_value[1];
const std::optional<float> opt_eval_value = evaluate_single_property(
rna_path, array_index, eval_time);
if (!opt_eval_value) {
return testing::AssertionFailure()
<< rna_path << "[" << array_index << "] should have been animated";
}
const float eval_value = *opt_eval_value;
const uint diff_ulps = ulp_diff_ff(expect_value, eval_value);
if (diff_ulps >= 4) {
return testing::AssertionFailure()
<< std::endl
<< " " << rna_path << "[" << array_index
<< "] evaluation did not produce the expected result:" << std::endl
<< " evaluated to: " << testing::PrintToString(eval_value) << std::endl
<< " expected : " << testing::PrintToString(expect_value) << std::endl;
}
return testing::AssertionSuccess();
};
/** Evaluate the layer, and test that the given property is not part of the result. */
testing::AssertionResult test_evaluate_layer_no_result(const StringRefNull rna_path,
const int array_index,
const float eval_time)
{
const std::optional<float> eval_value = evaluate_single_property(
rna_path, array_index, eval_time);
if (eval_value) {
return testing::AssertionFailure()
<< std::endl
<< " " << rna_path << "[" << array_index
<< "] evaluation should NOT produce a value:" << std::endl
<< " evaluated to: " << testing::PrintToString(*eval_value) << std::endl;
}
return testing::AssertionSuccess();
}
};
TEST_F(AnimationEvaluationTest, evaluate_layer__keyframes)
{
Strip &strip = layer->strip_add(*action, Strip::Type::Keyframe);
StripKeyframeData &strip_data = strip.data<StripKeyframeData>(*action);
/* Set some keys. */
strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.1f}, settings);
strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 47.5f}, settings);
strip_data.keyframe_insert(bmain, *slot, {"rotation_euler", 1}, {1.0f, 0.0f}, settings);
strip_data.keyframe_insert(bmain, *slot, {"rotation_euler", 1}, {5.0f, 3.14f}, settings);
/* Set the animated properties to some values. These should not be overwritten
* by the evaluation itself. */
cube->loc[0] = 3.0f;
cube->loc[1] = 2.0f;
cube->loc[2] = 7.0f;
cube->rot[0] = 3.0f;
cube->rot[1] = 2.0f;
cube->rot[2] = 7.0f;
/* Evaluate. */
anim_eval_context.eval_time = 3.0f;
EvaluationResult result = evaluate_layer(
cube_rna_ptr, *action, *layer, slot->handle, anim_eval_context);
/* Check the result. */
ASSERT_FALSE(result.is_empty());
AnimatedProperty *loc0_result = result.lookup_ptr(PropIdentifier("location", 0));
ASSERT_NE(nullptr, loc0_result) << "location[0] should have been animated";
EXPECT_EQ(47.3f, loc0_result->value);
EXPECT_EQ(3.0f, cube->loc[0]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(2.0f, cube->loc[1]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(7.0f, cube->loc[2]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(3.0f, cube->rot[0]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(2.0f, cube->rot[1]) << "Evaluation should not modify the animated ID";
EXPECT_EQ(7.0f, cube->rot[2]) << "Evaluation should not modify the animated ID";
}
TEST_F(AnimationEvaluationTest, strip_boundaries__single_strip)
{
/* Single finite strip, check first, middle, and last frame. */
Strip &strip = layer->strip_add(*action, Strip::Type::Keyframe);
strip.resize(1.0f, 10.0f);
/* Set some keys. */
StripKeyframeData &strip_data = strip.data<StripKeyframeData>(*action);
strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
/* Evaluate the layer to see how it handles the boundaries + something in between. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 48.0f}));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.001f));
}
TEST_F(AnimationEvaluationTest, strip_boundaries__nonoverlapping)
{
/* Two finite strips that are strictly distinct. */
Strip &strip1 = layer->strip_add(*action, Strip::Type::Keyframe);
Strip &strip2 = layer->strip_add(*action, Strip::Type::Keyframe);
strip1.resize(1.0f, 10.0f);
strip2.resize(11.0f, 20.0f);
strip2.frame_offset = 10;
/* Set some keys. */
{
StripKeyframeData &strip_data1 = strip1.data<StripKeyframeData>(*action);
strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
}
{
StripKeyframeData &strip_data2 = strip2.data<StripKeyframeData>(*action);
strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
}
/* Check Strip 1. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 48.0f}));
/* Check Strip 2. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {11.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {13.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {20.0f, 48.0f}));
/* Check outside the range of the strips. */
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 0.999f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.001f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.999f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 20.001f));
}
TEST_F(AnimationEvaluationTest, strip_boundaries__overlapping_edge)
{
/* Two finite strips that are overlapping on their edge. */
Strip &strip1 = layer->strip_add(*action, Strip::Type::Keyframe);
Strip &strip2 = layer->strip_add(*action, Strip::Type::Keyframe);
strip1.resize(1.0f, 10.0f);
strip2.resize(10.0f, 19.0f);
strip2.frame_offset = 9;
/* Set some keys. */
{
StripKeyframeData &strip_data1 = strip1.data<StripKeyframeData>(*action);
strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
}
{
StripKeyframeData &strip_data2 = strip2.data<StripKeyframeData>(*action);
strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
}
/* Check Strip 1. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
/* Check overlapping frame. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 47.0f}))
<< "On the overlapping frame, only Strip 2 should be evaluated.";
/* Check Strip 2. */
EXPECT_TRUE(test_evaluate_layer("location", 0, {12.0f, 187.0f}));
EXPECT_TRUE(test_evaluate_layer("location", 0, {19.0f, 48.0f}));
/* Check outside the range of the strips. */
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 0.999f));
EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 19.001f));
}
class AccessibleEvaluationResult : public EvaluationResult {
public:
EvaluationMap &get_map()
{
return result_;
}
};
TEST(AnimationEvaluationResultTest, prop_identifier_hashing)
{
AccessibleEvaluationResult result;
/* Test storing the same result twice, with different memory locations of the RNA paths. This
* tests that the mapping uses the actual string, and not just pointer comparison. */
const char *rna_path_1 = "pose.bones['Root'].location";
const std::string rna_path_2(rna_path_1);
ASSERT_NE(rna_path_1, rna_path_2.c_str())
<< "This test requires different addresses for the RNA path strings";
PathResolvedRNA fake_resolved_rna;
result.store(rna_path_1, 0, 1.0f, fake_resolved_rna);
result.store(rna_path_2, 0, 2.0f, fake_resolved_rna);
EXPECT_EQ(1, result.get_map().size())
<< "Storing a result for the same property twice should just overwrite the previous value";
{
PropIdentifier key(rna_path_1, 0);
AnimatedProperty *anim_prop = result.lookup_ptr(key);
EXPECT_EQ(2.0f, anim_prop->value) << "The last-stored result should survive.";
}
{
PropIdentifier key(rna_path_2, 0);
AnimatedProperty *anim_prop = result.lookup_ptr(key);
EXPECT_EQ(2.0f, anim_prop->value) << "The last-stored result should survive.";
}
}
} // namespace blender::animrig::tests