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