diff options
author | Campbell Barton <ideasman42@gmail.com> | 2021-03-26 05:12:56 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2021-03-26 05:37:40 +0300 |
commit | e18091650b92ee88339d1c8032572dabce21362a (patch) | |
tree | 5d1afc2c9e2bdebd638bfeb17a6d4bfd94f21624 /source/blender/blenkernel | |
parent | ff017df318d5e2dce8c918735e363c0e43458a7c (diff) |
Animation: action mirror RNA API using pose contents
This adds a new RNA method `Action.flip_with_pose(ob)` to flip the
action channels that control a pose.
The rest-pose is used to properly flip the bones transformation.
This is useful as a way to flip actions used in pose libraries,
so the same action need not be included for each side.
Reviewed By: sybren
Ref D10781
Diffstat (limited to 'source/blender/blenkernel')
-rw-r--r-- | source/blender/blenkernel/BKE_action.h | 3 | ||||
-rw-r--r-- | source/blender/blenkernel/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/action_mirror.c | 457 |
3 files changed, 461 insertions, 0 deletions
diff --git a/source/blender/blenkernel/BKE_action.h b/source/blender/blenkernel/BKE_action.h index 717cfa607ad..62315643647 100644 --- a/source/blender/blenkernel/BKE_action.h +++ b/source/blender/blenkernel/BKE_action.h @@ -227,6 +227,9 @@ void BKE_pose_blend_read_data(struct BlendDataReader *reader, struct bPose *pose void BKE_pose_blend_read_lib(struct BlendLibReader *reader, struct Object *ob, struct bPose *pose); void BKE_pose_blend_read_expand(struct BlendExpander *expander, struct bPose *pose); +/* action_mirror.c */ +void BKE_action_flip_with_pose(struct bAction *act, struct Object *ob_arm); + #ifdef __cplusplus }; #endif diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 12a801545a9..8fbf49b31d4 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -69,6 +69,7 @@ set(SRC intern/CCGSubSurf_util.c intern/DerivedMesh.cc intern/action.c + intern/action_mirror.c intern/addon.c intern/anim_data.c intern/anim_path.c diff --git a/source/blender/blenkernel/intern/action_mirror.c b/source/blender/blenkernel/intern/action_mirror.c new file mode 100644 index 00000000000..c975d2bfb9c --- /dev/null +++ b/source/blender/blenkernel/intern/action_mirror.c @@ -0,0 +1,457 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup bke + * + * Mirror/Symmetry functions applying to actions. + */ + +#include <math.h> +#include <string.h> + +#include "MEM_guardedalloc.h" + +#include "DNA_anim_types.h" +#include "DNA_armature_types.h" +#include "DNA_object_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math.h" +#include "BLI_string_utils.h" +#include "BLI_utildefines.h" + +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_fcurve.h" + +#include "DEG_depsgraph.h" + +/* -------------------------------------------------------------------- */ +/** \name Flip the Action (Armature/Pose Objects) + * + * This flips the action using the rest pose (not the evaluated pose). + * + * Details: + * + * - Key-frames are modified in-place, creating new key-frames is not yet supported. + * That could be useful if a user for example only has 2x rotation channels set. + * In practice users typically keyframe all rotation channels or none. + * + * - F-curve modifiers are disabled for evaluation, + * so the values written back to the keyframes don't include modifier offsets. + * + * - Sub-frame key-frames aren't supported, + * this could be added if needed without much trouble. + * + * - F-curves must have a #FCurve.bezt array (sampled curves aren't supported). + * \{ */ + +/** + * This structure is created for each pose channels F-curve, + * an action be evaluated and stored in `fcurve_eval`, + * with the mirrored values written into `bezt_array`. + * + * Store F-curve evaluated values, constructed with a sorted array of rounded keyed-frames, + * passed to #action_flip_pchan_cache_init. + */ +struct FCurve_KeyCache { + /** + * When NULL, ignore this channel. + */ + FCurve *fcurve; + /** + * Cached evaluated F-curve values (without modifiers). + */ + float *fcurve_eval; + /** + * Cached #FCurve.bezt values, NULL when no key-frame exists on this frame. + * + * \note The case where two keyframes round to the same frame isn't supported. + * In this case only the first will be used. + */ + BezTriple **bezt_array; +}; + +/** + * Assign `fkc` path, using a `path` lookup for a single value. + */ +static void action_flip_pchan_cache_fcurve_assign_value(struct FCurve_KeyCache *fkc, + int index, + const char *path, + struct FCurvePathCache *fcache) +{ + FCurve *fcu = BKE_fcurve_pathcache_find(fcache, path, index); + if (fcu && fcu->bezt) { + fkc->fcurve = fcu; + } +} + +/** + * Assign #FCurve_KeyCache.fcurve path, using a `path` lookup for an array. + */ +static void action_flip_pchan_cache_fcurve_assign_array(struct FCurve_KeyCache *fkc, + int fkc_len, + const char *path, + struct FCurvePathCache *fcache) +{ + FCurve **fcurves = alloca(sizeof(*fcurves) * fkc_len); + if (BKE_fcurve_pathcache_find_array(fcache, path, fcurves, fkc_len)) { + for (int i = 0; i < fkc_len; i++) { + if (fcurves[i] && fcurves[i]->bezt) { + fkc[i].fcurve = fcurves[i]; + } + } + } +} + +/** + * Fill in pose channel cache for each frame in `keyed_frames`. + * + * \param keyed_frames: An array of keyed_frames to evaluate, + * note that each frame is rounded to the nearest int. + * \param keyed_frames_len: The length of the `keyed_frames` array. + */ +static void action_flip_pchan_cache_init(struct FCurve_KeyCache *fkc, + const float *keyed_frames, + int keyed_frames_len) +{ + BLI_assert(fkc->fcurve != NULL); + + /* Cache the F-curve values for `keyed_frames`. */ + const int fcurve_flag = fkc->fcurve->flag; + fkc->fcurve->flag |= FCURVE_MOD_OFF; + fkc->fcurve_eval = MEM_mallocN(sizeof(float) * keyed_frames_len, __func__); + for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) { + const float evaltime = keyed_frames[frame_index]; + fkc->fcurve_eval[frame_index] = evaluate_fcurve_only_curve(fkc->fcurve, evaltime); + } + fkc->fcurve->flag = fcurve_flag; + + /* Cache the #BezTriple for `keyed_frames`, or leave as NULL. */ + fkc->bezt_array = MEM_mallocN(sizeof(*fkc->bezt_array) * keyed_frames_len, __func__); + BezTriple *bezt = fkc->fcurve->bezt; + BezTriple *bezt_end = fkc->fcurve->bezt + fkc->fcurve->totvert; + + int frame_index = 0; + while (frame_index < keyed_frames_len) { + const float evaltime = keyed_frames[frame_index]; + const float bezt_time = roundf(bezt->vec[1][0]); + if (bezt_time > evaltime) { + fkc->bezt_array[frame_index++] = NULL; + } + else { + if (bezt_time == evaltime) { + fkc->bezt_array[frame_index++] = bezt; + } + bezt++; + if (bezt == bezt_end) { + break; + } + } + } + /* Clear remaining unset keyed_frames (if-any). */ + while (frame_index < keyed_frames_len) { + fkc->bezt_array[frame_index++] = NULL; + } +} + +/** + */ +static void action_flip_pchan(Object *ob_arm, + const bPoseChannel *pchan, + struct FCurvePathCache *fcache) +{ + /* Begin F-Curve pose channel value extraction. */ + /* Use a fixed buffer size as it's known this can only be at most: + * `pose.bones["{MAXBONENAME}"].rotation_quaternion`. */ + char path_xform[256]; + char pchan_name_esc[sizeof(((bActionChannel *)NULL)->name) * 2]; + BLI_str_escape(pchan_name_esc, pchan->name, sizeof(pchan_name_esc)); + const int path_xform_prefix_len = SNPRINTF(path_xform, "pose.bones[\"%s\"]", pchan_name_esc); + char *path_xform_suffix = path_xform + path_xform_prefix_len; + const int path_xform_suffix_len = sizeof(path_xform) - path_xform_prefix_len; + + /* Lookup and assign all available #FCurve channels, + * unavailable channels are left NULL. */ + + /** + * Structure to store transformation F-curves corresponding to a pose bones transformation. + * Match struct member names from #bPoseChannel so macros avoid repetition. + * + * \note There is no need to read values unless they influence the 4x4 transform matrix, + * and no need to write values back unless they would be changed by a modified matrix. + * So `rotmode` needs to be read, but doesn't need to be written back to. + * + * Most bendy-bone settings don't need to be included either, flipping their RNA paths is enough. + * Although the X/Y settings could make sense to transform, in practice it would only + * work well if the rotation happened to swap X/Y alignment, leave this for now. + */ + struct { + struct FCurve_KeyCache loc[3], eul[3], quat[4], rotAxis[3], rotAngle, size[3], rotmode; + } fkc_pchan = {{{NULL}}}; + +#define FCURVE_ASSIGN_VALUE(id, path_test_suffix, index) \ + BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \ + action_flip_pchan_cache_fcurve_assign_value(&fkc_pchan.id, index, path_xform, fcache) + +#define FCURVE_ASSIGN_ARRAY(id, path_test_suffix) \ + BLI_strncpy(path_xform_suffix, path_test_suffix, path_xform_suffix_len); \ + action_flip_pchan_cache_fcurve_assign_array( \ + fkc_pchan.id, ARRAY_SIZE(fkc_pchan.id), path_xform, fcache) + + FCURVE_ASSIGN_ARRAY(loc, ".location"); + FCURVE_ASSIGN_ARRAY(eul, ".rotation_euler"); + FCURVE_ASSIGN_ARRAY(quat, ".rotation_quaternion"); + FCURVE_ASSIGN_ARRAY(rotAxis, ".rotation_axis_angle"); + FCURVE_ASSIGN_VALUE(rotAngle, ".rotation_axis_angle", 3); + FCURVE_ASSIGN_ARRAY(size, ".scale"); + FCURVE_ASSIGN_VALUE(rotmode, ".rotation_mode", 0); + +#undef FCURVE_ASSIGN_VALUE +#undef FCURVE_ASSIGN_ARRAY + + /* Array of F-curves, for convenient access. */ +#define FCURVE_CHANNEL_LEN (sizeof(fkc_pchan) / sizeof(struct FCurve_KeyCache)) + FCurve *fcurve_array[FCURVE_CHANNEL_LEN]; + int fcurve_array_len = 0; + + for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { + struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; + if (fkc->fcurve != NULL) { + fcurve_array[fcurve_array_len++] = fkc->fcurve; + } + } + + /* If this pose has no transform channels, there is nothing to do. */ + if (fcurve_array_len == 0) { + return; + } + + /* Calculate an array of frames used by any of the key-frames in `fcurve_array`. */ + int keyed_frames_len; + const float *keyed_frames = BKE_fcurves_calc_keyed_frames( + fcurve_array, fcurve_array_len, &keyed_frames_len); + + /* Initialize the pose channel curve cache from the F-curve. */ + for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { + struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; + if (fkc->fcurve == NULL) { + continue; + } + action_flip_pchan_cache_init(fkc, keyed_frames, keyed_frames_len); + } + + /* X-axis flipping matrix. */ + float flip_mtx[4][4]; + unit_m4(flip_mtx); + flip_mtx[0][0] = -1; + + bPoseChannel *pchan_flip = NULL; + char pchan_name_flip[MAXBONENAME]; + BLI_string_flip_side_name(pchan_name_flip, pchan->name, false, sizeof(pchan_name_flip)); + if (!STREQ(pchan_name_flip, pchan->name)) { + pchan_flip = BKE_pose_channel_find_name(ob_arm->pose, pchan_name_flip); + } + + float arm_mat_inv[4][4]; + invert_m4_m4(arm_mat_inv, pchan_flip ? pchan_flip->bone->arm_mat : pchan->bone->arm_mat); + + /* Now flip the transformation & write it back to the F-curves in `fkc_pchan`. */ + + for (int frame_index = 0; frame_index < keyed_frames_len; frame_index++) { + + /* Temporary pose channel to write values into, + * using the `fkc_pchan` values, falling back to the values in the pose channel. */ + bPoseChannel pchan_temp = *pchan; + + /* Load the values into the channel. */ +#define READ_VALUE_FLT(id) \ + if (fkc_pchan.id.fcurve_eval != NULL) { \ + pchan_temp.id = fkc_pchan.id.fcurve_eval[frame_index]; \ + } \ + ((void)0) + +#define READ_VALUE_INT(id) \ + if (fkc_pchan.id.fcurve_eval != NULL) { \ + pchan_temp.id = floorf(fkc_pchan.id.fcurve_eval[frame_index] + 0.5f); \ + } \ + ((void)0) + +#define READ_ARRAY_FLT(id) \ + for (int i = 0; i < ARRAY_SIZE(pchan_temp.id); i++) { \ + READ_VALUE_FLT(id[i]); \ + } \ + ((void)0) + + READ_ARRAY_FLT(loc); + READ_ARRAY_FLT(eul); + READ_ARRAY_FLT(quat); + READ_ARRAY_FLT(rotAxis); + READ_VALUE_FLT(rotAngle); + READ_ARRAY_FLT(size); + READ_VALUE_INT(rotmode); + +#undef READ_ARRAY_FLT +#undef READ_VALUE_FLT +#undef READ_VALUE_INT + + float chan_mat[4][4]; + BKE_pchan_to_mat4(&pchan_temp, chan_mat); + + /* Move to the pose-space. */ + mul_m4_m4m4(chan_mat, pchan->bone->arm_mat, chan_mat); + + /* Flip the matrix. */ + mul_m4_m4m4(chan_mat, chan_mat, flip_mtx); + mul_m4_m4m4(chan_mat, flip_mtx, chan_mat); + + /* Move back to bone-space space, using the flipped bone if it exists. */ + mul_m4_m4m4(chan_mat, arm_mat_inv, chan_mat); + + BKE_pchan_apply_mat4(&pchan_temp, chan_mat, false); + + /* Write the values back to the F-curves. */ +#define WRITE_VALUE_FLT(id) \ + if (fkc_pchan.id.fcurve_eval != NULL) { \ + BezTriple *bezt = fkc_pchan.id.bezt_array[frame_index]; \ + if (bezt != NULL) { \ + const float delta = pchan_temp.id - bezt->vec[1][1]; \ + bezt->vec[0][1] += delta; \ + bezt->vec[1][1] += delta; \ + bezt->vec[2][1] += delta; \ + } \ + } \ + ((void)0) + +#define WRITE_ARRAY_FLT(id) \ + for (int i = 0; i < ARRAY_SIZE(pchan_temp.id); i++) { \ + WRITE_VALUE_FLT(id[i]); \ + } \ + ((void)0) + + /* Write the values back the the F-curves. */ + WRITE_ARRAY_FLT(loc); + WRITE_ARRAY_FLT(eul); + WRITE_ARRAY_FLT(quat); + WRITE_ARRAY_FLT(rotAxis); + WRITE_VALUE_FLT(rotAngle); + WRITE_ARRAY_FLT(size); + /* No need to write back 'rotmode' as it can't be transformed. */ + +#undef WRITE_ARRAY_FLT +#undef WRITE_VALUE_FLT + } + + /* Recalculate handles. */ + for (int i = 0; i < fcurve_array_len; i++) { + calchandles_fcurve_ex(fcurve_array[i], 0); + } + + MEM_freeN((void *)keyed_frames); + + for (int chan = 0; chan < FCURVE_CHANNEL_LEN; chan++) { + struct FCurve_KeyCache *fkc = (struct FCurve_KeyCache *)(&fkc_pchan) + chan; + if (fkc->fcurve_eval) { + MEM_freeN(fkc->fcurve_eval); + } + if (fkc->bezt_array) { + MEM_freeN(fkc->bezt_array); + } + } +} + +/** + * Swap all RNA paths left/right. + */ +static void action_flip_pchan_rna_paths(struct bAction *act) +{ + const char *path_pose_prefix = "pose.bones[\""; + const int path_pose_prefix_len = strlen(path_pose_prefix); + + /* Tag curves that have renamed f-curves. */ + LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) { + agrp->flag &= ~AGRP_TEMP; + } + + LISTBASE_FOREACH (FCurve *, fcu, &act->curves) { + if (!STRPREFIX(fcu->rna_path, path_pose_prefix)) { + continue; + } + + const char *name_esc = fcu->rna_path + path_pose_prefix_len; + const char *name_esc_end = BLI_str_escape_find_quote(name_esc); + + /* While unlikely, an RNA path could be malformed. */ + if (UNLIKELY(name_esc_end == NULL)) { + continue; + } + + char name[MAXBONENAME]; + const size_t name_esc_len = (size_t)(name_esc_end - name_esc); + const size_t name_len = BLI_str_unescape(name, name_esc, name_esc_len); + + /* While unlikely, data paths could be constructed that have longer names than + * are currently supported. */ + if (UNLIKELY(name_len >= sizeof(name))) { + continue; + } + + /* When the flipped name differs, perform the rename. */ + char name_flip[MAXBONENAME]; + BLI_string_flip_side_name(name_flip, name, false, sizeof(name_flip)); + if (!STREQ(name_flip, name)) { + char name_flip_esc[MAXBONENAME * 2]; + BLI_str_escape(name_flip_esc, name_flip, sizeof(name_flip_esc)); + char *path_flip = BLI_sprintfN("pose.bones[\"%s%s", name_flip_esc, name_esc_end); + MEM_freeN(fcu->rna_path); + fcu->rna_path = path_flip; + + if (fcu->grp != NULL) { + fcu->grp->flag |= AGRP_TEMP; + } + } + } + + /* Rename tagged groups. */ + LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) { + if ((agrp->flag & AGRP_TEMP) == 0) { + continue; + } + agrp->flag &= ~AGRP_TEMP; + char name_flip[MAXBONENAME]; + BLI_string_flip_side_name(name_flip, agrp->name, false, sizeof(name_flip)); + if (!STREQ(name_flip, agrp->name)) { + STRNCPY(agrp->name, name_flip); + } + } +} + +void BKE_action_flip_with_pose(struct bAction *act, struct Object *ob_arm) +{ + struct FCurvePathCache *fcache = BKE_fcurve_pathcache_create(&act->curves); + int i; + LISTBASE_FOREACH_INDEX (bPoseChannel *, pchan, &ob_arm->pose->chanbase, i) { + action_flip_pchan(ob_arm, pchan, fcache); + } + BKE_fcurve_pathcache_destroy(fcache); + + action_flip_pchan_rna_paths(act); + + DEG_id_tag_update(&act->id, ID_RECALC_COPY_ON_WRITE); +} + +/** \} */ |