Blender V4.5
action_mirror.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include <cmath>
12#include <cstring>
13
14#include "MEM_guardedalloc.h"
15
16#include "DNA_anim_types.h"
17#include "DNA_armature_types.h"
18#include "DNA_object_types.h"
19
20#include "BLI_listbase.h"
21#include "BLI_math_matrix.h"
22#include "BLI_math_vector.h"
23#include "BLI_string.h"
24#include "BLI_string_utils.hh"
25#include "BLI_utildefines.h"
26
27#include "BKE_action.hh"
28#include "BKE_armature.hh"
29#include "BKE_fcurve.hh"
30
31#include "ANIM_action_legacy.hh"
32
33#include "DEG_depsgraph.hh"
34
35using namespace blender;
36
37/* -------------------------------------------------------------------- */
56
82
87 int index,
88 const char *path,
89 FCurvePathCache *fcache)
90{
91 FCurve *fcu = BKE_fcurve_pathcache_find(fcache, path, index);
92 if (fcu && fcu->bezt) {
93 fkc->fcurve = fcu;
94 }
95}
96
101 int fkc_len,
102 const char *path,
103 FCurvePathCache *fcache)
104{
105 FCurve **fcurves = static_cast<FCurve **>(alloca(sizeof(*fcurves) * fkc_len));
106 if (BKE_fcurve_pathcache_find_array(fcache, path, fcurves, fkc_len)) {
107 for (int i = 0; i < fkc_len; i++) {
108 if (fcurves[i] && fcurves[i]->bezt) {
109 fkc[i].fcurve = fcurves[i];
110 }
111 }
112 }
113}
114
123 const float *keyed_frames,
124 int keyed_frames_len)
125{
126 BLI_assert(fkc->fcurve != nullptr);
127
128 /* Cache the F-Curve values for `keyed_frames`. */
129 const int fcurve_flag = fkc->fcurve->flag;
130 fkc->fcurve->flag |= FCURVE_MOD_OFF;
131 fkc->fcurve_eval = MEM_malloc_arrayN<float>(size_t(keyed_frames_len), __func__);
132 for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) {
133 const float evaltime = keyed_frames[frame_index];
134 fkc->fcurve_eval[frame_index] = evaluate_fcurve_only_curve(fkc->fcurve, evaltime);
135 }
136 fkc->fcurve->flag = fcurve_flag;
137
138 /* Cache the #BezTriple for `keyed_frames`, or leave as nullptr. */
139 fkc->bezt_array = MEM_malloc_arrayN<BezTriple *>(size_t(keyed_frames_len), __func__);
140 BezTriple *bezt = fkc->fcurve->bezt;
141 BezTriple *bezt_end = fkc->fcurve->bezt + fkc->fcurve->totvert;
142
143 int frame_index = 0;
144 while (frame_index < keyed_frames_len) {
145 const float evaltime = keyed_frames[frame_index];
146 const float bezt_time = roundf(bezt->vec[1][0]);
147 if (bezt_time > evaltime) {
148 fkc->bezt_array[frame_index++] = nullptr;
149 }
150 else {
151 if (bezt_time == evaltime) {
152 fkc->bezt_array[frame_index++] = bezt;
153 }
154 bezt++;
155 if (bezt == bezt_end) {
156 break;
157 }
158 }
159 }
160 /* Clear remaining unset keyed_frames (if-any). */
161 while (frame_index < keyed_frames_len) {
162 fkc->bezt_array[frame_index++] = nullptr;
163 }
164}
165
168static void action_flip_pchan(Object *ob_arm, const bPoseChannel *pchan, FCurvePathCache *fcache)
169{
170 /* Begin F-Curve pose channel value extraction. */
171 /* Use a fixed buffer size as it's known this can only be at most:
172 * `pose.bones["{MAXBONENAME}"].rotation_quaternion`. */
173 char path_xform[256];
174 char pchan_name_esc[sizeof(bActionChannel::name) * 2];
175 BLI_str_escape(pchan_name_esc, pchan->name, sizeof(pchan_name_esc));
176 const int path_xform_prefix_len = SNPRINTF(path_xform, "pose.bones[\"%s\"]", pchan_name_esc);
177 char *path_xform_suffix = path_xform + path_xform_prefix_len;
178 const int path_xform_suffix_maxncpy = sizeof(path_xform) - path_xform_prefix_len;
179
180 /* Lookup and assign all available #FCurve channels,
181 * unavailable channels are left nullptr. */
182
195 struct {
196 FCurve_KeyCache loc[3], eul[3], quat[4], rotAxis[3], rotAngle, scale[3], rotmode;
197 } fkc_pchan = {{{nullptr}}};
198
199#define FCURVE_ASSIGN_VALUE(id, path_test_suffix, index) \
200 BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_maxncpy); \
201 action_flip_pchan_cache_fcurve_assign_value(&fkc_pchan.id, index, path_xform, fcache)
202
203#define FCURVE_ASSIGN_ARRAY(id, path_test_suffix) \
204 BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_maxncpy); \
205 action_flip_pchan_cache_fcurve_assign_array( \
206 fkc_pchan.id, ARRAY_SIZE(fkc_pchan.id), path_xform, fcache)
207
208 FCURVE_ASSIGN_ARRAY(loc, ".location");
209 FCURVE_ASSIGN_ARRAY(eul, ".rotation_euler");
210 FCURVE_ASSIGN_ARRAY(quat, ".rotation_quaternion");
211 FCURVE_ASSIGN_ARRAY(rotAxis, ".rotation_axis_angle");
212 FCURVE_ASSIGN_VALUE(rotAngle, ".rotation_axis_angle", 3);
213 FCURVE_ASSIGN_ARRAY(scale, ".scale");
214 FCURVE_ASSIGN_VALUE(rotmode, ".rotation_mode", 0);
215
216#undef FCURVE_ASSIGN_VALUE
217#undef FCURVE_ASSIGN_ARRAY
218
219/* Array of F-Curves, for convenient access. */
220#define FCURVE_CHANNEL_LEN (sizeof(fkc_pchan) / sizeof(FCurve_KeyCache))
221 FCurve *fcurve_array[FCURVE_CHANNEL_LEN];
222 int fcurve_array_len = 0;
223
224 for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) {
225 FCurve_KeyCache *fkc = (FCurve_KeyCache *)(&fkc_pchan) + chan;
226 if (fkc->fcurve != nullptr) {
227 fcurve_array[fcurve_array_len++] = fkc->fcurve;
228 }
229 }
230
231 /* If this pose has no transform channels, there is nothing to do. */
232 if (fcurve_array_len == 0) {
233 return;
234 }
235
236 /* Calculate an array of frames used by any of the key-frames in `fcurve_array`. */
237 int keyed_frames_len;
238 const float *keyed_frames = BKE_fcurves_calc_keyed_frames(
239 fcurve_array, fcurve_array_len, &keyed_frames_len);
240
241 /* Initialize the pose channel curve cache from the F-Curve. */
242 for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) {
243 FCurve_KeyCache *fkc = (FCurve_KeyCache *)(&fkc_pchan) + chan;
244 if (fkc->fcurve == nullptr) {
245 continue;
246 }
247 action_flip_pchan_cache_init(fkc, keyed_frames, keyed_frames_len);
248 }
249
250 /* X-axis flipping matrix. */
251 float flip_mtx[4][4];
252 unit_m4(flip_mtx);
253 flip_mtx[0][0] = -1;
254
255 bPoseChannel *pchan_flip = nullptr;
256 char pchan_name_flip[MAXBONENAME];
257 BLI_string_flip_side_name(pchan_name_flip, pchan->name, false, sizeof(pchan_name_flip));
258 if (!STREQ(pchan_name_flip, pchan->name)) {
259 pchan_flip = BKE_pose_channel_find_name(ob_arm->pose, pchan_name_flip);
260 }
261
262 float arm_mat_inv[4][4];
263 invert_m4_m4(arm_mat_inv, pchan_flip ? pchan_flip->bone->arm_mat : pchan->bone->arm_mat);
264
265 /* Now flip the transformation & write it back to the F-Curves in `fkc_pchan`. */
266
267 for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) {
268
269 /* Temporary pose channel to write values into,
270 * using the `fkc_pchan` values, falling back to the values in the pose channel. */
271 bPoseChannel pchan_temp = blender::dna::shallow_copy(*pchan);
272
273/* Load the values into the channel. */
274#define READ_VALUE_FLT(id) \
275 if (fkc_pchan.id.fcurve_eval != nullptr) { \
276 pchan_temp.id = fkc_pchan.id.fcurve_eval[frame_index]; \
277 } \
278 ((void)0)
279
280#define READ_VALUE_INT(id) \
281 if (fkc_pchan.id.fcurve_eval != nullptr) { \
282 pchan_temp.id = floorf(fkc_pchan.id.fcurve_eval[frame_index] + 0.5f); \
283 } \
284 ((void)0)
285
286#define READ_ARRAY_FLT(id) \
287 for (int i = 0; i < ARRAY_SIZE(pchan_temp.id); i++) { \
288 READ_VALUE_FLT(id[i]); \
289 } \
290 ((void)0)
291
292 READ_ARRAY_FLT(loc);
293 READ_ARRAY_FLT(eul);
294 READ_ARRAY_FLT(quat);
295 READ_ARRAY_FLT(rotAxis);
296 READ_VALUE_FLT(rotAngle);
297 READ_ARRAY_FLT(scale);
298 READ_VALUE_INT(rotmode);
299
300#undef READ_ARRAY_FLT
301#undef READ_VALUE_FLT
302#undef READ_VALUE_INT
303
304 float chan_mat[4][4];
305 BKE_pchan_to_mat4(&pchan_temp, chan_mat);
306
307 /* Move to the pose-space. */
308 mul_m4_m4m4(chan_mat, pchan->bone->arm_mat, chan_mat);
309
310 /* Flip the matrix. */
311 mul_m4_m4m4(chan_mat, chan_mat, flip_mtx);
312 mul_m4_m4m4(chan_mat, flip_mtx, chan_mat);
313
314 /* Move back to bone-space space, using the flipped bone if it exists. */
315 mul_m4_m4m4(chan_mat, arm_mat_inv, chan_mat);
316
317 /* The rest pose having an X-axis that is not mapping to a left/right direction (so aligned
318 * with the Y or Z axis) creates issues when flipping the pose. Instead of a negative scale on
319 * the X-axis, it turns into a 180 degree rotation over the Y-axis.
320 * This has only been observed with bones that can't be flipped,
321 * hence the check for `pchan_flip`. */
322 const float unit_x[3] = {1.0f, 0.0f, 0.0f};
323 const bool is_x_axis_orthogonal = (pchan_flip == nullptr) &&
324 (fabsf(dot_v3v3(pchan->bone->arm_mat[0], unit_x)) <= 1e-6f);
325 if (is_x_axis_orthogonal) {
326 /* Matrix needs to flip both the X and Z axes to come out right. */
327 float extra_mat[4][4] = {
328 {-1.0f, 0.0f, 0.0f, 0.0f},
329 {0.0f, 1.0f, 0.0f, 0.0f},
330 {0.0f, 0.0f, -1.0f, 0.0f},
331 {0.0f, 0.0f, 0.0f, 1.0f},
332 };
333 mul_m4_m4m4(chan_mat, extra_mat, chan_mat);
334 }
335
336 BKE_pchan_apply_mat4(&pchan_temp, chan_mat, false);
337
338/* Write the values back to the F-Curves. */
339#define WRITE_VALUE_FLT(id) \
340 if (fkc_pchan.id.fcurve_eval != nullptr) { \
341 BezTriple *bezt = fkc_pchan.id.bezt_array[frame_index]; \
342 if (bezt != nullptr) { \
343 const float delta = pchan_temp.id - bezt->vec[1][1]; \
344 bezt->vec[0][1] += delta; \
345 bezt->vec[1][1] += delta; \
346 bezt->vec[2][1] += delta; \
347 } \
348 } \
349 ((void)0)
350
351#define WRITE_ARRAY_FLT(id) \
352 for (int i = 0; i < ARRAY_SIZE(pchan_temp.id); i++) { \
353 WRITE_VALUE_FLT(id[i]); \
354 } \
355 ((void)0)
356
357 /* Write the values back the F-Curves. */
358 WRITE_ARRAY_FLT(loc);
359 WRITE_ARRAY_FLT(eul);
360 WRITE_ARRAY_FLT(quat);
361 WRITE_ARRAY_FLT(rotAxis);
362 WRITE_VALUE_FLT(rotAngle);
363 WRITE_ARRAY_FLT(scale);
364 /* No need to write back 'rotmode' as it can't be transformed. */
365
366#undef WRITE_ARRAY_FLT
367#undef WRITE_VALUE_FLT
368 }
369
370 /* Recalculate handles. */
371 for (int i = 0; i < fcurve_array_len; i++) {
373 }
374
375 MEM_freeN(keyed_frames);
376
377 for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) {
378 FCurve_KeyCache *fkc = (FCurve_KeyCache *)(&fkc_pchan) + chan;
379 if (fkc->fcurve_eval) {
381 }
382 if (fkc->bezt_array) {
383 MEM_freeN(fkc->bezt_array);
384 }
385 }
386}
387
392{
393 const char *path_pose_prefix = "pose.bones[\"";
394 const int path_pose_prefix_len = strlen(path_pose_prefix);
395
396 /* Tag curves that have renamed f-curves. */
398 agrp->flag &= ~AGRP_TEMP;
399 }
400
402 if (!STRPREFIX(fcu->rna_path, path_pose_prefix)) {
403 continue;
404 }
405
406 const char *name_esc = fcu->rna_path + path_pose_prefix_len;
407 const char *name_esc_end = BLI_str_escape_find_quote(name_esc);
408
409 /* While unlikely, an RNA path could be malformed. */
410 if (UNLIKELY(name_esc_end == nullptr)) {
411 continue;
412 }
413
414 char name[MAXBONENAME];
415 const size_t name_esc_len = size_t(name_esc_end - name_esc);
416 const size_t name_len = BLI_str_unescape(name, name_esc, name_esc_len);
417
418 /* While unlikely, data paths could be constructed that have longer names than
419 * are currently supported. */
420 if (UNLIKELY(name_len >= sizeof(name))) {
421 continue;
422 }
423
424 /* When the flipped name differs, perform the rename. */
425 char name_flip[MAXBONENAME];
426 BLI_string_flip_side_name(name_flip, name, false, sizeof(name_flip));
427 if (!STREQ(name_flip, name)) {
428 char name_flip_esc[MAXBONENAME * 2];
429 BLI_str_escape(name_flip_esc, name_flip, sizeof(name_flip_esc));
430 char *path_flip = BLI_sprintfN("pose.bones[\"%s%s", name_flip_esc, name_esc_end);
431 MEM_freeN(fcu->rna_path);
432 fcu->rna_path = path_flip;
433
434 if (fcu->grp != nullptr) {
435 fcu->grp->flag |= AGRP_TEMP;
436 }
437 }
438 }
439
440 /* Rename tagged groups. */
442 if ((agrp->flag & AGRP_TEMP) == 0) {
443 continue;
444 }
445 agrp->flag &= ~AGRP_TEMP;
446 char name_flip[MAXBONENAME];
447 BLI_string_flip_side_name(name_flip, agrp->name, false, sizeof(name_flip));
448 if (!STREQ(name_flip, agrp->name)) {
449 STRNCPY(agrp->name, name_flip);
450 }
451 }
452}
453
455{
456 animrig::Action &action = act->wrap();
457 if (action.slot_array_num == 0) {
458 /* Cannot flip an empty action. */
459 return;
460 }
461 blender::Set<animrig::Slot *> flipped_slots;
462 for (Object *object : objects) {
463 animrig::Slot *slot = animrig::generic_slot_for_autoassign(object->id, action, "");
464 if (!slot) {
465 slot = action.slot(0);
466 }
467 if (!flipped_slots.add(slot)) {
468 continue;
469 }
472 LISTBASE_FOREACH (bPoseChannel *, pchan, &object->pose->chanbase) {
473 action_flip_pchan(object, pchan, fcache);
474 }
476 }
477
479
481}
482
Functions for backward compatibility with the legacy Action API.
Blender kernel action and pose functionality.
bPoseChannel * BKE_pose_channel_find_name(const bPose *pose, const char *name)
void BKE_pchan_apply_mat4(bPoseChannel *pchan, const float mat[4][4], bool use_compat)
Definition armature.cc:2389
void BKE_pchan_to_mat4(const bPoseChannel *pchan, float r_chanmat[4][4])
Definition armature.cc:2947
float * BKE_fcurves_calc_keyed_frames(FCurve **fcurve_array, int fcurve_array_len, int *r_frames_len)
void BKE_fcurve_pathcache_destroy(FCurvePathCache *fcache)
FCurvePathCache * BKE_fcurve_pathcache_create(blender::Span< FCurve * > fcurves)
void BKE_fcurve_handles_recalc_ex(FCurve *fcu, eBezTriple_Flag handle_sel_flag)
FCurve * BKE_fcurve_pathcache_find(const FCurvePathCache *fcache, const char rna_path[], int array_index)
float evaluate_fcurve_only_curve(const FCurve *fcu, float evaltime)
int BKE_fcurve_pathcache_find_array(const FCurvePathCache *fcache, const char *rna_path, FCurve **fcurve_result, int fcurve_result_len)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
void mul_m4_m4m4(float R[4][4], const float A[4][4], const float B[4][4])
bool invert_m4_m4(float inverse[4][4], const float mat[4][4])
void unit_m4(float m[4][4])
MINLINE float dot_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
char * BLI_sprintfN(const char *__restrict format,...) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1
size_t size_t size_t const char * BLI_str_escape_find_quote(const char *str) ATTR_NONNULL(1)
Definition string.cc:448
size_t size_t size_t BLI_str_unescape(char *__restrict dst, const char *__restrict src, size_t src_maxncpy) ATTR_NONNULL(1
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
size_t BLI_str_escape(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
size_t BLI_string_flip_side_name(char *name_dst, const char *name_src, bool strip_number, size_t name_dst_maxncpy) ATTR_NONNULL(1
#define STRPREFIX(a, b)
#define UNLIKELY(x)
#define STREQ(a, b)
void DEG_id_tag_update(ID *id, unsigned int flags)
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1026
@ AGRP_TEMP
@ FCURVE_MOD_OFF
#define MAXBONENAME
eBezTriple_Flag
Object is a sort of wrapper for general info.
Read Guarded memory(de)allocation.
#define READ_VALUE_FLT(id)
static void action_flip_pchan_rna_paths(bAction *act)
static void action_flip_pchan_cache_fcurve_assign_value(FCurve_KeyCache *fkc, int index, const char *path, FCurvePathCache *fcache)
#define WRITE_ARRAY_FLT(id)
static void action_flip_pchan_cache_init(FCurve_KeyCache *fkc, const float *keyed_frames, int keyed_frames_len)
static void action_flip_pchan_cache_fcurve_assign_array(FCurve_KeyCache *fkc, int fkc_len, const char *path, FCurvePathCache *fcache)
#define READ_VALUE_INT(id)
#define FCURVE_ASSIGN_ARRAY(id, path_test_suffix)
#define FCURVE_CHANNEL_LEN
void BKE_action_flip_with_pose(bAction *act, blender::Span< Object * > objects)
static void action_flip_pchan(Object *ob_arm, const bPoseChannel *pchan, FCurvePathCache *fcache)
#define WRITE_VALUE_FLT(id)
#define READ_ARRAY_FLT(id)
#define FCURVE_ASSIGN_VALUE(id, path_test_suffix, index)
float evaltime
bool add(const Key &key)
Definition BLI_set.hh:248
const Slot * slot(int64_t index) const
#define fabsf(x)
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
Vector< const FCurve * > fcurves_all(const bAction *action)
Vector< bActionGroup * > channel_groups_all(bAction *action)
Span< FCurve * > fcurves_for_action_slot(Action &action, slot_handle_t slot_handle)
Slot * generic_slot_for_autoassign(const ID &animated_id, Action &action, StringRefNull last_slot_identifier)
float vec[3][3]
float arm_mat[4][4]
BezTriple ** bezt_array
BezTriple * bezt
unsigned int totvert
struct bPose * pose
struct Bone * bone
i
Definition text_draw.cc:230