Blender V5.0
evaluation.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Developers
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include "ANIM_evaluation.hh"
6
7#include "BKE_animsys.h"
8#include "BKE_fcurve.hh"
9
10#include "BLI_map.hh"
11#include "BLI_math_base.hh"
12
13#include "CLG_log.h"
14
16
17static CLG_LogRef LOG = {"anim.evaluation"};
18
19namespace blender::animrig {
20
21using namespace internal;
22
28 const EvaluationResult &current_result,
29 const Layer &current_layer);
30
37void apply_evaluation_result(const EvaluationResult &evaluation_result,
38 PointerRNA &animated_id_ptr,
39 bool flush_to_original);
40
42 Action &action,
43 const slot_handle_t slot_handle,
44 const AnimationEvalContext &anim_eval_context)
45{
46 EvaluationResult last_result;
47
48 /* Evaluate each layer in order. */
49 for (Layer *layer : action.layers()) {
50 if (layer->influence <= 0.0f) {
51 /* Don't bother evaluating layers without influence. */
52 continue;
53 }
54
55 auto layer_result = evaluate_layer(
56 animated_id_ptr, action, *layer, slot_handle, anim_eval_context);
57 if (!layer_result) {
58 continue;
59 }
60
61 if (!last_result) {
62 /* Simple case: no results so far, so just use this layer as-is. There is
63 * nothing to blend/combine with, so ignore the influence and combination
64 * options. */
65 last_result = layer_result;
66 continue;
67 }
68
69 /* Complex case: blend this layer's result into the previous layer's result. */
70 last_result = blend_layer_results(last_result, layer_result, *layer);
71 }
72
73 return last_result;
74}
75
77 Action &action,
78 const slot_handle_t slot_handle,
79 const AnimationEvalContext &anim_eval_context,
80 const bool flush_to_original)
81{
82 EvaluationResult evaluation_result = evaluate_action(
83 animated_id_ptr, action, slot_handle, anim_eval_context);
84 if (!evaluation_result) {
85 return;
86 }
87
88 apply_evaluation_result(evaluation_result, animated_id_ptr, flush_to_original);
89}
90
91/* Copy of the same-named function in anim_sys.cc, with the check on action groups removed. */
92static bool is_fcurve_evaluatable(const FCurve *fcu)
93{
94 if (fcu->rna_path == nullptr) {
95 return false;
96 }
97
98 /* Not checking for FCURVE_DISABLED here, because those FCurves may still be evaluatable for
99 * other users of the same slot. See #135666. This is safe to do since this function isn't called
100 * for drivers. */
101 if (fcu->flag & FCURVE_MUTED) {
102 return false;
103 }
104 if (BKE_fcurve_is_empty(fcu)) {
105 return false;
106 }
107 return true;
108}
109
110/* Copy of the same-named function in anim_sys.cc, but with the special handling for NLA strips
111 * removed. */
113{
114 *ptr_orig = *ptr;
115 /* Original note from anim_sys.cc:
116 * -----------
117 * NOTE: nlastrip_evaluate_controls() creates PointerRNA with ID of nullptr. Technically, this is
118 * not a valid pointer, but there are exceptions in various places of this file which handles
119 * such pointers.
120 * We do special trickery here as well, to quickly go from evaluated to original NlaStrip.
121 * -----------
122 * And this is all not ported to the new layered animation system. */
123 BLI_assert_msg(ptr->owner_id, "NLA support was not ported to the layered animation system");
124 ptr_orig->owner_id = ptr_orig->owner_id->orig_id;
125 ptr_orig->data = ptr_orig->owner_id;
126}
127
128/* Copy of the same-named function in anim_sys.cc. */
130 const char *rna_path,
131 const int array_index,
132 const float value)
133{
134 PointerRNA ptr_orig;
136
137 PathResolvedRNA orig_anim_rna;
138 /* TODO(sergey): Should be possible to cache resolved path in dependency graph somehow. */
139 if (BKE_animsys_rna_path_resolve(&ptr_orig, rna_path, array_index, &orig_anim_rna)) {
140 BKE_animsys_write_to_rna_path(&orig_anim_rna, value);
141 }
142}
143
145 StripKeyframeData &strip_data,
146 const slot_handle_t slot_handle,
147 const AnimationEvalContext &offset_eval_context)
148{
149 Channelbag *channelbag_for_slot = strip_data.channelbag_for_slot(slot_handle);
150 if (!channelbag_for_slot) {
151 return {};
152 }
153
154 EvaluationResult evaluation_result;
155 for (FCurve *fcu : channelbag_for_slot->fcurves()) {
156 /* Blatant copy of animsys_evaluate_fcurves(). */
157
158 if (!is_fcurve_evaluatable(fcu)) {
159 continue;
160 }
161
162 PathResolvedRNA anim_rna;
164 &animated_id_ptr, fcu->rna_path, fcu->array_index, &anim_rna))
165 {
166 /* Log this at quite a high level, because it can get _very_ noisy when playing back
167 * animation. */
169 "Cannot resolve RNA path %s[%d] on ID %s\n",
170 fcu->rna_path,
171 fcu->array_index,
172 animated_id_ptr.owner_id->name);
173 continue;
174 }
175
176 const float curval = calculate_fcurve(&anim_rna, fcu, &offset_eval_context);
177 evaluation_result.store(fcu->rna_path, fcu->array_index, curval, anim_rna);
178 }
179
180 return evaluation_result;
181}
182
183void apply_evaluation_result(const EvaluationResult &evaluation_result,
184 PointerRNA &animated_id_ptr,
185 const bool flush_to_original)
186{
187 for (auto channel_result : evaluation_result.items()) {
188 const PropIdentifier &prop_ident = channel_result.key;
189 const AnimatedProperty &anim_prop = channel_result.value;
190 const float animated_value = anim_prop.value;
191 PathResolvedRNA anim_rna = anim_prop.prop_rna;
192
193 BKE_animsys_write_to_rna_path(&anim_rna, animated_value);
194
195 if (flush_to_original) {
196 /* Convert the StringRef to a `const char *`, as the rest of the RNA path handling code in
197 * BKE still uses `char *` instead of `StringRef`. */
198 animsys_write_orig_anim_rna(&animated_id_ptr,
199 StringRefNull(prop_ident.rna_path).c_str(),
200 prop_ident.array_index,
201 animated_value);
202 }
203 }
204}
205
207 Action &owning_action,
208 Strip &strip,
209 const slot_handle_t slot_handle,
210 const AnimationEvalContext &anim_eval_context)
211{
212 AnimationEvalContext offset_eval_context = anim_eval_context;
213 /* Positive offset means the entire strip is pushed "to the right", so
214 * evaluation needs to happen further "to the left". */
215 offset_eval_context.eval_time -= strip.frame_offset;
216
217 switch (strip.type()) {
218 case Strip::Type::Keyframe: {
219 StripKeyframeData &strip_data = strip.data<StripKeyframeData>(owning_action);
220 return evaluate_keyframe_data(animated_id_ptr, strip_data, slot_handle, offset_eval_context);
221 }
222 }
223
224 return {};
225}
226
228 const EvaluationResult &current_result,
229 const Layer &current_layer)
230{
231 /* TODO?: store the layer results sequentially, so that we can step through
232 * them in parallel, instead of iterating over one and doing map lookups on
233 * the other. */
234
235 EvaluationResult blend = last_result;
236
237 for (auto channel_result : current_result.items()) {
238 const PropIdentifier &prop_ident = channel_result.key;
239 AnimatedProperty *last_prop = blend.lookup_ptr(prop_ident);
240 const AnimatedProperty &anim_prop = channel_result.value;
241
242 if (!last_prop) {
243 /* Nothing to blend with, so just take (influence * value). */
244 blend.store(prop_ident.rna_path,
245 prop_ident.array_index,
246 anim_prop.value * current_layer.influence,
247 anim_prop.prop_rna);
248 continue;
249 }
250
251 /* TODO: move this to a separate function. And write more smartness for rotations. */
252 switch (current_layer.mix_mode()) {
254 last_prop->value = anim_prop.value * current_layer.influence;
255 break;
257 last_prop->value = math::interpolate(
258 current_layer.influence, last_prop->value, anim_prop.value);
259 break;
261 last_prop->value += anim_prop.value * current_layer.influence;
262 break;
264 last_prop->value -= anim_prop.value * current_layer.influence;
265 break;
267 last_prop->value *= anim_prop.value * current_layer.influence;
268 break;
269 };
270 }
271
272 return blend;
273}
274
275namespace internal {
276
278 Action &owning_action,
279 Layer &layer,
280 const slot_handle_t slot_handle,
281 const AnimationEvalContext &anim_eval_context)
282{
283 /* TODO: implement cross-blending between overlapping strips. For now, this is not supported.
284 * Instead, the first strong result is taken (see below), and if that is not available, the last
285 * weak result will be used.
286 *
287 * Weak result: obtained from evaluating the final frame of the strip.
288 * Strong result: any result that is not a weak result. */
289 EvaluationResult last_weak_result;
290
291 for (Strip *strip : layer.strips()) {
292 if (!strip->contains_frame(anim_eval_context.eval_time)) {
293 continue;
294 }
295
296 const EvaluationResult strip_result = evaluate_strip(
297 animated_id_ptr, owning_action, *strip, slot_handle, anim_eval_context);
298 if (!strip_result) {
299 continue;
300 }
301
302 const bool is_weak_result = strip->is_last_frame(anim_eval_context.eval_time);
303 if (is_weak_result) {
304 /* Keep going until a strong result is found. */
305 last_weak_result = strip_result;
306 continue;
307 }
308
309 /* Found a strong result, just return it. */
310 return strip_result;
311 }
312
313 return last_weak_result;
314}
315
316} // namespace internal
317
318} // namespace blender::animrig
Layered Action evaluation.
bool BKE_animsys_write_to_rna_path(struct PathResolvedRNA *anim_rna, float value, bool force_write=false)
Definition anim_sys.cc:460
bool BKE_animsys_rna_path_resolve(struct PointerRNA *ptr, const char *rna_path, int array_index, struct PathResolvedRNA *r_result)
Definition anim_sys.cc:350
bool BKE_fcurve_is_empty(const FCurve *fcu)
float calculate_fcurve(PathResolvedRNA *anim_rna, FCurve *fcu, const AnimationEvalContext *anim_eval_context)
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define CLOG_DEBUG(clg_ref,...)
Definition CLG_log.h:191
@ FCURVE_MUTED
constexpr const char * c_str() const
blender::Span< const Layer * > layers() const
blender::Span< const FCurve * > fcurves() const
void store(const StringRefNull rna_path, const int array_index, const float value, const PathResolvedRNA &prop_rna)
EvaluationMap::ItemIterator items() const
MixMode mix_mode() const
blender::Span< const Strip * > strips() const
const Channelbag * channelbag_for_slot(const Slot &slot) const
const T & data(const Action &owning_action) const
#define LOG(level)
Definition log.h:97
EvaluationResult evaluate_layer(PointerRNA &animated_id_ptr, Action &owning_action, Layer &layer, const slot_handle_t slot_handle, const AnimationEvalContext &anim_eval_context)
static void animsys_write_orig_anim_rna(PointerRNA *ptr, const char *rna_path, const int array_index, const float value)
void evaluate_and_apply_action(PointerRNA &animated_id_ptr, Action &action, slot_handle_t slot_handle, const AnimationEvalContext &anim_eval_context, bool flush_to_original)
Definition evaluation.cc:76
static bool is_fcurve_evaluatable(const FCurve *fcu)
Definition evaluation.cc:92
EvaluationResult evaluate_action(PointerRNA &animated_id_ptr, Action &action, slot_handle_t slot_handle, const AnimationEvalContext &anim_eval_context)
Definition evaluation.cc:41
static void animsys_construct_orig_pointer_rna(const PointerRNA *ptr, PointerRNA *ptr_orig)
EvaluationResult blend_layer_results(const EvaluationResult &last_result, const EvaluationResult &current_result, const Layer &current_layer)
decltype(::ActionSlot::handle) slot_handle_t
void apply_evaluation_result(const EvaluationResult &evaluation_result, PointerRNA &animated_id_ptr, bool flush_to_original)
static EvaluationResult evaluate_keyframe_data(PointerRNA &animated_id_ptr, StripKeyframeData &strip_data, const slot_handle_t slot_handle, const AnimationEvalContext &offset_eval_context)
static EvaluationResult evaluate_strip(PointerRNA &animated_id_ptr, Action &owning_action, Strip &strip, const slot_handle_t slot_handle, const AnimationEvalContext &anim_eval_context)
T interpolate(const T &a, const T &b, const FactorT &t)
char * rna_path
char name[258]
Definition DNA_ID.h:432
struct ID * orig_id
Definition DNA_ID.h:501
ID * owner_id
Definition RNA_types.hh:51
void * data
Definition RNA_types.hh:53
static int blend(const Tex *tex, const float texvec[3], TexResult *texres)
PointerRNA * ptr
Definition wm_files.cc:4238