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
		
			
				
	
	
		
			458 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			458 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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);
 | |
| }
 | |
| 
 | |
| /** \} */
 |