Blender V4.3
evaluation_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "ANIM_action.hh"
6#include "ANIM_evaluation.hh"
8
9#include "BKE_action.hh"
10#include "BKE_animsys.h"
11#include "BKE_idtype.hh"
12#include "BKE_lib_id.hh"
13#include "BKE_main.hh"
14#include "BKE_object.hh"
15
16#include "DNA_object_types.h"
17
18#include "RNA_access.hh"
19#include "RNA_prototypes.hh"
20
21#include "BLI_math_base.h"
22#include "BLI_string_utf8.h"
23
24#include <optional>
25
26#include "CLG_log.h"
27#include "testing/testing.h"
28
30
31using namespace blender::animrig::internal;
32
33class AnimationEvaluationTest : public testing::Test {
34 protected:
40
44
45 public:
46 static void SetUpTestSuite()
47 {
48 /* BKE_id_free() hits a code path that uses CLOG, which crashes if not initialized properly. */
49 CLG_init();
50
51 /* To make id_can_have_animdata() and friends work, the `id_types` array needs to be set up. */
53 }
54
55 static void TearDownTestSuite()
56 {
57 CLG_exit();
58 }
59
60 void SetUp() override
61 {
63 action = static_cast<Action *>(BKE_id_new(bmain, ID_AC, "ACÄnimåtië"));
64
66
67 slot = &action->slot_add();
69
70 layer = &action->layer_add("Kübus layer");
71
72 /* Make it easier to predict test values. */
73 settings.interpolation = BEZT_IPO_LIN;
74
75 cube_rna_ptr = RNA_pointer_create(&cube->id, &RNA_Object, &cube->id);
76 }
77
78 void TearDown() override
79 {
81 }
82
84 std::optional<float> evaluate_single_property(const StringRefNull rna_path,
85 const int array_index,
86 const float eval_time)
87 {
88 anim_eval_context.eval_time = eval_time;
91
92 const AnimatedProperty *loc0_result = result.lookup_ptr(PropIdentifier(rna_path, array_index));
93 if (!loc0_result) {
94 return {};
95 }
96 return loc0_result->value;
97 }
98
100 testing::AssertionResult test_evaluate_layer(const StringRefNull rna_path,
101 const int array_index,
102 const float2 eval_time__expect_value)
103 {
104 const float eval_time = eval_time__expect_value[0];
105 const float expect_value = eval_time__expect_value[1];
106
107 const std::optional<float> opt_eval_value = evaluate_single_property(
108 rna_path, array_index, eval_time);
109 if (!opt_eval_value) {
110 return testing::AssertionFailure()
111 << rna_path << "[" << array_index << "] should have been animated";
112 }
113
114 const float eval_value = *opt_eval_value;
115 const uint diff_ulps = ulp_diff_ff(expect_value, eval_value);
116 if (diff_ulps >= 4) {
117 return testing::AssertionFailure()
118 << std::endl
119 << " " << rna_path << "[" << array_index
120 << "] evaluation did not produce the expected result:" << std::endl
121 << " evaluted to: " << testing::PrintToString(eval_value) << std::endl
122 << " expected : " << testing::PrintToString(expect_value) << std::endl;
123 }
124
125 return testing::AssertionSuccess();
126 };
127
129 testing::AssertionResult test_evaluate_layer_no_result(const StringRefNull rna_path,
130 const int array_index,
131 const float eval_time)
132 {
133 const std::optional<float> eval_value = evaluate_single_property(
134 rna_path, array_index, eval_time);
135 if (eval_value) {
136 return testing::AssertionFailure()
137 << std::endl
138 << " " << rna_path << "[" << array_index
139 << "] evaluation should NOT produce a value:" << std::endl
140 << " evaluted to: " << testing::PrintToString(*eval_value) << std::endl;
141 }
142
143 return testing::AssertionSuccess();
144 }
145};
146
147TEST_F(AnimationEvaluationTest, evaluate_layer__keyframes)
148{
149 Strip &strip = layer->strip_add(*action, Strip::Type::Keyframe);
150 StripKeyframeData &strip_data = strip.data<StripKeyframeData>(*action);
151
152 /* Set some keys. */
153 strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.1f}, settings);
154 strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 47.5f}, settings);
155 strip_data.keyframe_insert(bmain, *slot, {"rotation_euler", 1}, {1.0f, 0.0f}, settings);
156 strip_data.keyframe_insert(bmain, *slot, {"rotation_euler", 1}, {5.0f, 3.14f}, settings);
157
158 /* Set the animated properties to some values. These should not be overwritten
159 * by the evaluation itself. */
160 cube->loc[0] = 3.0f;
161 cube->loc[1] = 2.0f;
162 cube->loc[2] = 7.0f;
163 cube->rot[0] = 3.0f;
164 cube->rot[1] = 2.0f;
165 cube->rot[2] = 7.0f;
166
167 /* Evaluate. */
168 anim_eval_context.eval_time = 3.0f;
170 cube_rna_ptr, *action, *layer, slot->handle, anim_eval_context);
171
172 /* Check the result. */
173 ASSERT_FALSE(result.is_empty());
174 AnimatedProperty *loc0_result = result.lookup_ptr(PropIdentifier("location", 0));
175 ASSERT_NE(nullptr, loc0_result) << "location[0] should have been animated";
176 EXPECT_EQ(47.3f, loc0_result->value);
177
178 EXPECT_EQ(3.0f, cube->loc[0]) << "Evaluation should not modify the animated ID";
179 EXPECT_EQ(2.0f, cube->loc[1]) << "Evaluation should not modify the animated ID";
180 EXPECT_EQ(7.0f, cube->loc[2]) << "Evaluation should not modify the animated ID";
181 EXPECT_EQ(3.0f, cube->rot[0]) << "Evaluation should not modify the animated ID";
182 EXPECT_EQ(2.0f, cube->rot[1]) << "Evaluation should not modify the animated ID";
183 EXPECT_EQ(7.0f, cube->rot[2]) << "Evaluation should not modify the animated ID";
184}
185
186TEST_F(AnimationEvaluationTest, strip_boundaries__single_strip)
187{
188 /* Single finite strip, check first, middle, and last frame. */
189 Strip &strip = layer->strip_add(*action, Strip::Type::Keyframe);
190 strip.resize(1.0f, 10.0f);
191
192 /* Set some keys. */
193 StripKeyframeData &strip_data = strip.data<StripKeyframeData>(*action);
194 strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
195 strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
196 strip_data.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
197
198 /* Evaluate the layer to see how it handles the boundaries + something in between. */
199 EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
200 EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
201 EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 48.0f}));
202
203 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.001f));
204}
205
206TEST_F(AnimationEvaluationTest, strip_boundaries__nonoverlapping)
207{
208 /* Two finite strips that are strictly distinct. */
209 Strip &strip1 = layer->strip_add(*action, Strip::Type::Keyframe);
210 Strip &strip2 = layer->strip_add(*action, Strip::Type::Keyframe);
211 strip1.resize(1.0f, 10.0f);
212 strip2.resize(11.0f, 20.0f);
213 strip2.frame_offset = 10;
214
215 /* Set some keys. */
216 {
217 StripKeyframeData &strip_data1 = strip1.data<StripKeyframeData>(*action);
218 strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
219 strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
220 strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
221 }
222 {
223 StripKeyframeData &strip_data2 = strip2.data<StripKeyframeData>(*action);
224 strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
225 strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
226 strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
227 }
228
229 /* Check Strip 1. */
230 EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
231 EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
232 EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 48.0f}));
233
234 /* Check Strip 2. */
235 EXPECT_TRUE(test_evaluate_layer("location", 0, {11.0f, 47.0f}));
236 EXPECT_TRUE(test_evaluate_layer("location", 0, {13.0f, 187.0f}));
237 EXPECT_TRUE(test_evaluate_layer("location", 0, {20.0f, 48.0f}));
238
239 /* Check outside the range of the strips. */
240 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 0.999f));
241 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.001f));
242 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 10.999f));
243 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 20.001f));
244}
245
246TEST_F(AnimationEvaluationTest, strip_boundaries__overlapping_edge)
247{
248 /* Two finite strips that are overlapping on their edge. */
249 Strip &strip1 = layer->strip_add(*action, Strip::Type::Keyframe);
250 Strip &strip2 = layer->strip_add(*action, Strip::Type::Keyframe);
251 strip1.resize(1.0f, 10.0f);
252 strip2.resize(10.0f, 19.0f);
253 strip2.frame_offset = 9;
254
255 /* Set some keys. */
256 {
257 StripKeyframeData &strip_data1 = strip1.data<StripKeyframeData>(*action);
258 strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
259 strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
260 strip_data1.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
261 }
262 {
263 StripKeyframeData &strip_data2 = strip2.data<StripKeyframeData>(*action);
264 strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {1.0f, 47.0f}, settings);
265 strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {5.0f, 327.0f}, settings);
266 strip_data2.keyframe_insert(bmain, *slot, {"location", 0}, {10.0f, 48.0f}, settings);
267 }
268
269 /* Check Strip 1. */
270 EXPECT_TRUE(test_evaluate_layer("location", 0, {1.0f, 47.0f}));
271 EXPECT_TRUE(test_evaluate_layer("location", 0, {3.0f, 187.0f}));
272
273 /* Check overlapping frame. */
274 EXPECT_TRUE(test_evaluate_layer("location", 0, {10.0f, 47.0f}))
275 << "On the overlapping frame, only Strip 2 should be evaluated.";
276
277 /* Check Strip 2. */
278 EXPECT_TRUE(test_evaluate_layer("location", 0, {12.0f, 187.0f}));
279 EXPECT_TRUE(test_evaluate_layer("location", 0, {19.0f, 48.0f}));
280
281 /* Check outside the range of the strips. */
282 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 0.999f));
283 EXPECT_TRUE(test_evaluate_layer_no_result("location", 0, 19.001f));
284}
285
287 public:
289 {
290 return result_;
291 }
292};
293
294TEST(AnimationEvaluationResultTest, prop_identifier_hashing)
295{
297
298 /* Test storing the same result twice, with different memory locations of the RNA paths. This
299 * tests that the mapping uses the actual string, and not just pointer comparison. */
300 const char *rna_path_1 = "pose.bones['Root'].location";
301 const std::string rna_path_2(rna_path_1);
302 ASSERT_NE(rna_path_1, rna_path_2.c_str())
303 << "This test requires different addresses for the RNA path strings";
304
305 PathResolvedRNA fake_resolved_rna;
306 result.store(rna_path_1, 0, 1.0f, fake_resolved_rna);
307 result.store(rna_path_2, 0, 2.0f, fake_resolved_rna);
308 EXPECT_EQ(1, result.get_map().size())
309 << "Storing a result for the same property twice should just overwrite the previous value";
310
311 {
312 PropIdentifier key(rna_path_1, 0);
313 AnimatedProperty *anim_prop = result.lookup_ptr(key);
314 EXPECT_EQ(2.0f, anim_prop->value) << "The last-stored result should survive.";
315 }
316 {
317 PropIdentifier key(rna_path_2, 0);
318 AnimatedProperty *anim_prop = result.lookup_ptr(key);
319 EXPECT_EQ(2.0f, anim_prop->value) << "The last-stored result should survive.";
320 }
321}
322
323} // namespace blender::animrig::tests
Functions and classes to work with Actions.
Layered Action evaluation.
Blender kernel action and pose functionality.
void BKE_idtype_init()
Definition idtype.cc:127
void * BKE_id_new(Main *bmain, short type, const char *name)
Definition lib_id.cc:1482
Main * BKE_main_new(void)
Definition main.cc:45
void BKE_main_free(Main *bmain)
Definition main.cc:175
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
MINLINE uint ulp_diff_ff(float a, float b)
unsigned int uint
void CLG_exit(void)
Definition clog.c:706
void CLG_init(void)
Definition clog.c:699
@ ID_AC
@ BEZT_IPO_LIN
Object is a sort of wrapper for general info.
@ OB_EMPTY
Layer & layer_add(std::optional< StringRefNull > name)
SingleKeyingResult keyframe_insert(Main *bmain, const Slot &slot, FCurveDescriptor fcurve_descriptor, float2 time_value, const KeyframeSettings &settings, eInsertKeyFlags insert_key_flags=INSERTKEY_NOFLAGS)
void resize(float frame_start, float frame_end)
const T & data(const Action &owning_action) const
testing::AssertionResult test_evaluate_layer(const StringRefNull rna_path, const int array_index, const float2 eval_time__expect_value)
std::optional< float > evaluate_single_property(const StringRefNull rna_path, const int array_index, const float eval_time)
testing::AssertionResult test_evaluate_layer_no_result(const StringRefNull rna_path, const int array_index, const float eval_time)
EvaluationResult evaluate_layer(PointerRNA &animated_id_ptr, Action &owning_action, Layer &layer, const slot_handle_t slot_handle, const AnimationEvalContext &anim_eval_context)
TEST_F(ActionIteratorsTest, iterate_all_fcurves_of_slot)
TEST(ANIM_bone_collections, bonecoll_new_free)
KeyframeSettings get_keyframe_settings(bool from_userprefs)
ActionSlotAssignmentResult assign_action_and_slot(Action *action, Slot *slot_to_assign, ID &animated_id)
PointerRNA RNA_pointer_create(ID *id, StructRNA *type, void *data)