/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2008 Blender Foundation. */ /** \file * \ingroup edanimation */ #include #include #include #include #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" #include "BLI_lasso_2d.h" #include "BLI_math.h" #include "BLI_utildefines.h" #include "DNA_anim_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "BKE_fcurve.h" #include "BKE_nla.h" #include "ED_anim_api.h" #include "ED_keyframes_edit.h" #include "ED_markers.h" /* This file defines an API and set of callback-operators for * non-destructive editing of keyframe data. * * Two API functions are defined for actually performing the operations on the data: * ANIM_fcurve_keyframes_loop() * which take the data they operate on, a few callbacks defining what operations to perform. * * As operators which work on keyframes usually apply the same operation on all BezTriples in * every channel, the code has been optimized providing a set of functions which will get the * appropriate bezier-modify function to set. These functions (ANIM_editkeyframes_*) will need * to be called before getting any channels. * * A set of 'validation' callbacks are provided for checking if a BezTriple should be operated on. * These should only be used when using a 'general' BezTriple editor (i.e. selection setters which * don't check existing selection status). * * - Joshua Leung, Dec 2008 */ /* ************************************************************************** */ /* Keyframe Editing Loops - Exposed API */ /* --------------------------- Base Functions ------------------------------------ */ short ANIM_fcurve_keyframes_loop(KeyframeEditData *ked, FCurve *fcu, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { BezTriple *bezt; short ok = 0; uint i; /* sanity check */ if (ELEM(NULL, fcu, fcu->bezt)) { return 0; } /* Set the F-Curve into the edit-data so that it can be accessed. */ if (ked) { ked->fcu = fcu; ked->curIndex = 0; ked->curflags = ok; } /* if function to apply to bezier curves is set, then loop through executing it on beztriples */ if (key_cb) { /* if there's a validation func, include that check in the loop * (this is should be more efficient than checking for it in every loop) */ if (key_ok) { for (bezt = fcu->bezt, i = 0; i < fcu->totvert; bezt++, i++) { if (ked) { /* advance the index, and reset the ok flags (to not influence the result) */ ked->curIndex = i; ked->curflags = 0; } /* Only operate on this BezTriple if it fulfills the criteria of the validation func */ if ((ok = key_ok(ked, bezt))) { if (ked) { ked->curflags = ok; } /* Exit with return-code '1' if function returns positive * This is useful if finding if some BezTriple satisfies a condition. */ if (key_cb(ked, bezt)) { return 1; } } } } else { for (bezt = fcu->bezt, i = 0; i < fcu->totvert; bezt++, i++) { if (ked) { ked->curIndex = i; } /* Exit with return-code '1' if function returns positive * This is useful if finding if some BezTriple satisfies a condition. */ if (key_cb(ked, bezt)) { return 1; } } } } /* unset the F-Curve from the editdata now that it's done */ if (ked) { ked->fcu = NULL; ked->curIndex = 0; ked->curflags = 0; } /* if fcu_cb (F-Curve post-editing callback) has been specified then execute it */ if (fcu_cb) { fcu_cb(fcu); } /* done */ return 0; } /* --------------------- Further Abstracted (Not Exposed Directly) ----------------------------- */ /* This function is used to loop over the keyframe data in an Action Group */ static short agrp_keyframes_loop(KeyframeEditData *ked, bActionGroup *agrp, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { FCurve *fcu; /* sanity check */ if (agrp == NULL) { return 0; } /* only iterate over the F-Curves that are in this group */ for (fcu = agrp->channels.first; fcu && fcu->grp == agrp; fcu = fcu->next) { if (ANIM_fcurve_keyframes_loop(ked, fcu, key_ok, key_cb, fcu_cb)) { return 1; } } return 0; } /* This function is used to loop over the keyframe data in an Action */ static short act_keyframes_loop(KeyframeEditData *ked, bAction *act, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { FCurve *fcu; /* sanity check */ if (act == NULL) { return 0; } /* just loop through all F-Curves */ for (fcu = act->curves.first; fcu; fcu = fcu->next) { if (ANIM_fcurve_keyframes_loop(ked, fcu, key_ok, key_cb, fcu_cb)) { return 1; } } return 0; } /* This function is used to loop over the keyframe data in an Object */ static short ob_keyframes_loop(KeyframeEditData *ked, bDopeSheet *ads, Object *ob, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { bAnimContext ac = {NULL}; ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; int filter; int ret = 0; bAnimListElem dummychan = {NULL}; Base dummybase = {NULL}; if (ob == NULL) { return 0; } /* create a dummy wrapper data to work with */ dummybase.object = ob; dummychan.type = ANIMTYPE_OBJECT; dummychan.data = &dummybase; dummychan.id = &ob->id; dummychan.adt = ob->adt; ac.ads = ads; ac.data = &dummychan; ac.datatype = ANIMCONT_CHANNEL; /* get F-Curves to take keyframes from */ filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Loop through each F-Curve, applying the operation as required, * but stopping on the first one. */ for (ale = anim_data.first; ale; ale = ale->next) { if (ANIM_fcurve_keyframes_loop(ked, (FCurve *)ale->data, key_ok, key_cb, fcu_cb)) { ret = 1; break; } } ANIM_animdata_freelist(&anim_data); /* Return the return code (defaults to zero if nothing happened). */ return ret; } /* This function is used to loop over the keyframe data in a Scene */ static short scene_keyframes_loop(KeyframeEditData *ked, bDopeSheet *ads, Scene *sce, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { bAnimContext ac = {NULL}; ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; int filter; int ret = 0; bAnimListElem dummychan = {NULL}; if (sce == NULL) { return 0; } /* create a dummy wrapper data to work with */ dummychan.type = ANIMTYPE_SCENE; dummychan.data = sce; dummychan.id = &sce->id; dummychan.adt = sce->adt; ac.ads = ads; ac.data = &dummychan; ac.datatype = ANIMCONT_CHANNEL; /* get F-Curves to take keyframes from */ filter = ANIMFILTER_DATA_VISIBLE | ANIMFILTER_FCURVESONLY; ANIM_animdata_filter(&ac, &anim_data, filter, ac.data, ac.datatype); /* Loop through each F-Curve, applying the operation as required, * but stopping on the first one. */ for (ale = anim_data.first; ale; ale = ale->next) { if (ANIM_fcurve_keyframes_loop(ked, (FCurve *)ale->data, key_ok, key_cb, fcu_cb)) { ret = 1; break; } } ANIM_animdata_freelist(&anim_data); /* Return the return code (defaults to zero if nothing happened). */ return ret; } /* This function is used to loop over the keyframe data in a DopeSheet summary */ static short summary_keyframes_loop(KeyframeEditData *ked, bAnimContext *ac, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; int filter, ret_code = 0; /* sanity check */ if (ac == NULL) { return 0; } /* get F-Curves to take keyframes from */ filter = ANIMFILTER_DATA_VISIBLE; ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* loop through each F-Curve, working on the keyframes until the first curve aborts */ for (ale = anim_data.first; ale; ale = ale->next) { switch (ale->datatype) { case ALE_MASKLAY: case ALE_GPFRAME: break; case ALE_FCURVE: default: { if (ked && ked->iterflags) { /* make backups of the current values, so that a localized fix * (e.g. NLA time remapping) can be applied to these values */ float f1 = ked->f1; float f2 = ked->f2; if (ked->iterflags & (KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP)) { AnimData *adt = ANIM_nla_mapping_get(ac, ale); if (ked->iterflags & KED_F1_NLA_UNMAP) { ked->f1 = BKE_nla_tweakedit_remap(adt, f1, NLATIME_CONVERT_UNMAP); } if (ked->iterflags & KED_F2_NLA_UNMAP) { ked->f2 = BKE_nla_tweakedit_remap(adt, f2, NLATIME_CONVERT_UNMAP); } } /* now operate on the channel as per normal */ ret_code = ANIM_fcurve_keyframes_loop(ked, ale->data, key_ok, key_cb, fcu_cb); /* reset */ ked->f1 = f1; ked->f2 = f2; } else { /* no special handling required... */ ret_code = ANIM_fcurve_keyframes_loop(ked, ale->data, key_ok, key_cb, fcu_cb); } break; } } if (ret_code) { break; } } ANIM_animdata_freelist(&anim_data); return ret_code; } /* --- */ short ANIM_animchannel_keyframes_loop(KeyframeEditData *ked, bDopeSheet *ads, bAnimListElem *ale, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { /* sanity checks */ if (ale == NULL) { return 0; } /* method to use depends on the type of keyframe data */ switch (ale->datatype) { /* direct keyframe data (these loops are exposed) */ case ALE_FCURVE: /* F-Curve */ return ANIM_fcurve_keyframes_loop(ked, ale->key_data, key_ok, key_cb, fcu_cb); /* indirect 'summaries' (these are not exposed directly) * NOTE: must keep this code in sync with the drawing code and also the filtering code! */ case ALE_GROUP: /* action group */ return agrp_keyframes_loop(ked, (bActionGroup *)ale->data, key_ok, key_cb, fcu_cb); case ALE_ACT: /* action */ return act_keyframes_loop(ked, (bAction *)ale->key_data, key_ok, key_cb, fcu_cb); case ALE_OB: /* object */ return ob_keyframes_loop(ked, ads, (Object *)ale->key_data, key_ok, key_cb, fcu_cb); case ALE_SCE: /* scene */ return scene_keyframes_loop(ked, ads, (Scene *)ale->data, key_ok, key_cb, fcu_cb); case ALE_ALL: /* 'all' (DopeSheet summary) */ return summary_keyframes_loop(ked, (bAnimContext *)ale->data, key_ok, key_cb, fcu_cb); } return 0; } short ANIM_animchanneldata_keyframes_loop(KeyframeEditData *ked, bDopeSheet *ads, void *data, int keytype, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb) { /* sanity checks */ if (data == NULL) { return 0; } /* method to use depends on the type of keyframe data */ switch (keytype) { /* direct keyframe data (these loops are exposed) */ case ALE_FCURVE: /* F-Curve */ return ANIM_fcurve_keyframes_loop(ked, data, key_ok, key_cb, fcu_cb); /* indirect 'summaries' (these are not exposed directly) * NOTE: must keep this code in sync with the drawing code and also the filtering code! */ case ALE_GROUP: /* action group */ return agrp_keyframes_loop(ked, (bActionGroup *)data, key_ok, key_cb, fcu_cb); case ALE_ACT: /* action */ return act_keyframes_loop(ked, (bAction *)data, key_ok, key_cb, fcu_cb); case ALE_OB: /* object */ return ob_keyframes_loop(ked, ads, (Object *)data, key_ok, key_cb, fcu_cb); case ALE_SCE: /* scene */ return scene_keyframes_loop(ked, ads, (Scene *)data, key_ok, key_cb, fcu_cb); case ALE_ALL: /* 'all' (DopeSheet summary) */ return summary_keyframes_loop(ked, (bAnimContext *)data, key_ok, key_cb, fcu_cb); } return 0; } void ANIM_animdata_keyframe_callback(bAnimContext *ac, eAnimFilter_Flags filter, KeyframeEditFunc callback_fn) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); for (ale = anim_data.first; ale; ale = ale->next) { ANIM_fcurve_keyframes_loop(NULL, ale->key_data, NULL, callback_fn, BKE_fcurve_handles_recalc); ale->update |= ANIM_UPDATE_DEFAULT; } ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } /* ************************************************************************** */ /* Keyframe Integrity Tools */ void ANIM_editkeyframes_refresh(bAnimContext *ac) { ListBase anim_data = {NULL, NULL}; bAnimListElem *ale; int filter; /* filter animation data */ filter = ANIMFILTER_DATA_VISIBLE; ANIM_animdata_filter(ac, &anim_data, filter, ac->data, ac->datatype); /* Loop over F-Curves that are likely to have been edited, and tag them to * ensure the keyframes are in order and handles are in a valid position. */ for (ale = anim_data.first; ale; ale = ale->next) { ale->update |= ANIM_UPDATE_DEPS | ANIM_UPDATE_HANDLES | ANIM_UPDATE_ORDER; } /* free temp data */ ANIM_animdata_update(ac, &anim_data); ANIM_animdata_freelist(&anim_data); } /* ************************************************************************** */ /* BezTriple Validation Callbacks */ /* ------------------------ */ /* Some macros to make this easier... */ /* run the given check on the 3 handles: * - Check should be a macro, which takes the handle index as its single arg, * which it substitutes later. * - Requires that a var, of type short, is named 'ok', * and has been initialized to 0. */ #define KEYFRAME_OK_CHECKS(check) \ { \ CHECK_TYPE(ok, short); \ if (check(1)) { \ ok |= KEYFRAME_OK_KEY; \ } \ if (ked && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES)) { \ /* Only act on visible items, so check handle visibility state. */ \ const bool handles_visible = ((ked->iterflags & KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE) ? \ BEZT_ISSEL_ANY(bezt) : \ true); \ if (handles_visible) { \ if (check(0)) { \ ok |= KEYFRAME_OK_H1; \ } \ if (check(2)) { \ ok |= KEYFRAME_OK_H2; \ } \ } \ } \ } \ (void)0 /* ------------------------ */ static short ok_bezier_frame(KeyframeEditData *ked, BezTriple *bezt) { short ok = 0; /* frame is stored in f1 property (this float accuracy check may need to be dropped?) */ #define KEY_CHECK_OK(_index) IS_EQF(bezt->vec[_index][0], ked->f1) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } static short ok_bezier_framerange(KeyframeEditData *ked, BezTriple *bezt) { short ok = 0; /* frame range is stored in float properties */ #define KEY_CHECK_OK(_index) ((bezt->vec[_index][0] > ked->f1) && (bezt->vec[_index][0] < ked->f2)) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } static short ok_bezier_selected(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* this macro checks all beztriple handles for selection... * only one of the verts has to be selected for this to be ok... */ if (BEZT_ISSEL_ANY(bezt)) { return KEYFRAME_OK_ALL; } return 0; } static short ok_bezier_value(KeyframeEditData *ked, BezTriple *bezt) { short ok = 0; /* Value is stored in f1 property: * - This float accuracy check may need to be dropped? * - Should value be stored in f2 instead * so that we won't have conflicts when using f1 for frames too? */ #define KEY_CHECK_OK(_index) IS_EQF(bezt->vec[_index][1], ked->f1) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } static short ok_bezier_valuerange(KeyframeEditData *ked, BezTriple *bezt) { short ok = 0; /* value range is stored in float properties */ #define KEY_CHECK_OK(_index) ((bezt->vec[_index][1] > ked->f1) && (bezt->vec[_index][1] < ked->f2)) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } static short ok_bezier_region(KeyframeEditData *ked, BezTriple *bezt) { /* rect is stored in data property (it's of type rectf, but may not be set) */ if (ked->data) { short ok = 0; #define KEY_CHECK_OK(_index) BLI_rctf_isect_pt_v(ked->data, bezt->vec[_index]) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } return 0; } bool keyframe_region_lasso_test(const KeyframeEdit_LassoData *data_lasso, const float xy[2]) { if (BLI_rctf_isect_pt_v(data_lasso->rectf_scaled, xy)) { float xy_view[2]; BLI_rctf_transform_pt_v(data_lasso->rectf_view, data_lasso->rectf_scaled, xy_view, xy); if (BLI_lasso_is_point_inside( data_lasso->mcoords, data_lasso->mcoords_len, xy_view[0], xy_view[1], INT_MAX)) { return true; } } return false; } static short ok_bezier_region_lasso(KeyframeEditData *ked, BezTriple *bezt) { /* check for lasso customdata (KeyframeEdit_LassoData) */ if (ked->data) { short ok = 0; #define KEY_CHECK_OK(_index) keyframe_region_lasso_test(ked->data, bezt->vec[_index]) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } return 0; } static short ok_bezier_channel_lasso(KeyframeEditData *ked, BezTriple *bezt) { /* check for lasso customdata (KeyframeEdit_LassoData) */ if (ked->data) { KeyframeEdit_LassoData *data = ked->data; float pt[2]; /* late-binding remap of the x values (for summary channels) */ /* XXX: Ideally we reset, but it should be fine just leaving it as-is * as the next channel will reset it properly, while the next summary-channel * curve will also reset by itself... */ if (ked->iterflags & (KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP)) { data->rectf_scaled->xmin = ked->f1; data->rectf_scaled->xmax = ked->f2; } /* only use the x-coordinate of the point; the y is the channel range... */ pt[0] = bezt->vec[1][0]; pt[1] = ked->channel_y; if (keyframe_region_lasso_test(data, pt)) { return KEYFRAME_OK_KEY; } } return 0; } bool keyframe_region_circle_test(const KeyframeEdit_CircleData *data_circle, const float xy[2]) { if (BLI_rctf_isect_pt_v(data_circle->rectf_scaled, xy)) { float xy_view[2]; BLI_rctf_transform_pt_v(data_circle->rectf_view, data_circle->rectf_scaled, xy_view, xy); xy_view[0] = xy_view[0] - data_circle->mval[0]; xy_view[1] = xy_view[1] - data_circle->mval[1]; return len_squared_v2(xy_view) < data_circle->radius_squared; } return false; } static short ok_bezier_region_circle(KeyframeEditData *ked, BezTriple *bezt) { /* check for circle select customdata (KeyframeEdit_CircleData) */ if (ked->data) { short ok = 0; #define KEY_CHECK_OK(_index) keyframe_region_circle_test(ked->data, bezt->vec[_index]) KEYFRAME_OK_CHECKS(KEY_CHECK_OK); #undef KEY_CHECK_OK /* return ok flags */ return ok; } return 0; } static short ok_bezier_channel_circle(KeyframeEditData *ked, BezTriple *bezt) { /* check for circle select customdata (KeyframeEdit_CircleData) */ if (ked->data) { KeyframeEdit_CircleData *data = ked->data; float pt[2]; /* late-binding remap of the x values (for summary channels) */ /* XXX: Ideally we reset, but it should be fine just leaving it as-is * as the next channel will reset it properly, while the next summary-channel * curve will also reset by itself... */ if (ked->iterflags & (KED_F1_NLA_UNMAP | KED_F2_NLA_UNMAP)) { data->rectf_scaled->xmin = ked->f1; data->rectf_scaled->xmax = ked->f2; } /* only use the x-coordinate of the point; the y is the channel range... */ pt[0] = bezt->vec[1][0]; pt[1] = ked->channel_y; if (keyframe_region_circle_test(data, pt)) { return KEYFRAME_OK_KEY; } } return 0; } KeyframeEditFunc ANIM_editkeyframes_ok(short mode) { /* eEditKeyframes_Validate */ switch (mode) { case BEZT_OK_FRAME: /* only if bezt falls on the right frame (float) */ return ok_bezier_frame; case BEZT_OK_FRAMERANGE: /* only if bezt falls within the specified frame range (floats) */ return ok_bezier_framerange; case BEZT_OK_SELECTED: /* only if bezt is selected (self) */ return ok_bezier_selected; case BEZT_OK_VALUE: /* only if bezt value matches (float) */ return ok_bezier_value; case BEZT_OK_VALUERANGE: /* only if bezier falls within the specified value range (floats) */ return ok_bezier_valuerange; case BEZT_OK_REGION: /* only if bezier falls within the specified rect (data -> rectf) */ return ok_bezier_region; case BEZT_OK_REGION_LASSO: /* only if the point falls within KeyframeEdit_LassoData defined data */ return ok_bezier_region_lasso; case BEZT_OK_REGION_CIRCLE: /* only if the point falls within KeyframeEdit_CircleData defined data */ return ok_bezier_region_circle; case BEZT_OK_CHANNEL_LASSO: /* same as BEZT_OK_REGION_LASSO, but we're only using the x-value of the points */ return ok_bezier_channel_lasso; case BEZT_OK_CHANNEL_CIRCLE: /* same as BEZT_OK_REGION_CIRCLE, but we're only using the x-value of the points */ return ok_bezier_channel_circle; default: /* nothing was ok */ return NULL; } } /* ******************************************* */ /* Assorted Utility Functions */ short bezt_calc_average(KeyframeEditData *ked, BezTriple *bezt) { /* only if selected */ if (bezt->f2 & SELECT) { /* store average time in float 1 (only do rounding at last step) */ ked->f1 += bezt->vec[1][0]; /* store average value in float 2 (only do rounding at last step) * - this isn't always needed, but some operators may also require this */ ked->f2 += bezt->vec[1][1]; /* increment number of items */ ked->i1++; } return 0; } short bezt_to_cfraelem(KeyframeEditData *ked, BezTriple *bezt) { /* only if selected */ if (bezt->f2 & SELECT) { CfraElem *ce = MEM_callocN(sizeof(CfraElem), "cfraElem"); BLI_addtail(&ked->list, ce); ce->cfra = bezt->vec[1][0]; } return 0; } void bezt_remap_times(KeyframeEditData *ked, BezTriple *bezt) { KeyframeEditCD_Remap *rmap = (KeyframeEditCD_Remap *)ked->data; const float scale = (rmap->newMax - rmap->newMin) / (rmap->oldMax - rmap->oldMin); /* perform transform on all three handles unless indicated otherwise */ /* TODO: need to include some checks for that */ bezt->vec[0][0] = scale * (bezt->vec[0][0] - rmap->oldMin) + rmap->newMin; bezt->vec[1][0] = scale * (bezt->vec[1][0] - rmap->oldMin) + rmap->newMin; bezt->vec[2][0] = scale * (bezt->vec[2][0] - rmap->oldMin) + rmap->newMin; } /* ******************************************* */ /* Transform */ /* snaps the keyframe to the nearest frame */ static short snap_bezier_nearest(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->vec[1][0] = (float)floorf(bezt->vec[1][0] + 0.5f); } return 0; } /* snaps the keyframe to the nearest second */ static short snap_bezier_nearestsec(KeyframeEditData *ked, BezTriple *bezt) { const Scene *scene = ked->scene; const float secf = (float)FPS; if (bezt->f2 & SELECT) { bezt->vec[1][0] = (floorf(bezt->vec[1][0] / secf + 0.5f) * secf); } return 0; } /* snaps the keyframe to the current frame */ static short snap_bezier_cframe(KeyframeEditData *ked, BezTriple *bezt) { const Scene *scene = ked->scene; if (bezt->f2 & SELECT) { bezt->vec[1][0] = (float)scene->r.cfra; } return 0; } /* snaps the keyframe time to the nearest marker's frame */ static short snap_bezier_nearmarker(KeyframeEditData *ked, BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->vec[1][0] = (float)ED_markers_find_nearest_marker_time(&ked->list, bezt->vec[1][0]); } return 0; } /* make the handles have the same value as the key */ static short snap_bezier_horizontal(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->vec[0][1] = bezt->vec[2][1] = bezt->vec[1][1]; if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) { bezt->h1 = HD_ALIGN; } if (ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) { bezt->h2 = HD_ALIGN; } } return 0; } /* frame to snap to is stored in the custom data -> first float value slot */ static short snap_bezier_time(KeyframeEditData *ked, BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->vec[1][0] = ked->f1; } return 0; } /* value to snap to is stored in the custom data -> first float value slot */ static short snap_bezier_value(KeyframeEditData *ked, BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->vec[1][1] = ked->f1; } return 0; } KeyframeEditFunc ANIM_editkeyframes_snap(short mode) { /* eEditKeyframes_Snap */ switch (mode) { case SNAP_KEYS_NEARFRAME: /* snap to nearest frame */ return snap_bezier_nearest; case SNAP_KEYS_CURFRAME: /* snap to current frame */ return snap_bezier_cframe; case SNAP_KEYS_NEARMARKER: /* snap to nearest marker */ return snap_bezier_nearmarker; case SNAP_KEYS_NEARSEC: /* snap to nearest second */ return snap_bezier_nearestsec; case SNAP_KEYS_HORIZONTAL: /* snap handles to same value */ return snap_bezier_horizontal; case SNAP_KEYS_TIME: /* snap to given frame/time */ return snap_bezier_time; case SNAP_KEYS_VALUE: /* snap to given value */ return snap_bezier_value; default: /* just in case */ return snap_bezier_nearest; } } /* --------- */ static void mirror_bezier_xaxis_ex(BezTriple *bezt, const float center) { for (int i = 0; i < 3; i++) { float diff = (center - bezt->vec[i][0]); bezt->vec[i][0] = (center + diff); } swap_v3_v3(bezt->vec[0], bezt->vec[2]); SWAP(uint8_t, bezt->h1, bezt->h2); SWAP(uint8_t, bezt->f1, bezt->f3); } static void mirror_bezier_yaxis_ex(BezTriple *bezt, const float center) { for (int i = 0; i < 3; i++) { float diff = (center - bezt->vec[i][1]); bezt->vec[i][1] = (center + diff); } } static short mirror_bezier_cframe(KeyframeEditData *ked, BezTriple *bezt) { const Scene *scene = ked->scene; if (bezt->f2 & SELECT) { mirror_bezier_xaxis_ex(bezt, scene->r.cfra); } return 0; } static short mirror_bezier_yaxis(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { /* Yes, names are inverted, we are mirroring across y axis, hence along x axis... */ mirror_bezier_xaxis_ex(bezt, 0.0f); } return 0; } static short mirror_bezier_xaxis(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { /* Yes, names are inverted, we are mirroring across x axis, hence along y axis... */ mirror_bezier_yaxis_ex(bezt, 0.0f); } return 0; } static short mirror_bezier_marker(KeyframeEditData *ked, BezTriple *bezt) { /* mirroring time stored in f1 */ if (bezt->f2 & SELECT) { mirror_bezier_xaxis_ex(bezt, ked->f1); } return 0; } static short mirror_bezier_time(KeyframeEditData *ked, BezTriple *bezt) { /* value to mirror over is stored in f1 */ if (bezt->f2 & SELECT) { mirror_bezier_xaxis_ex(bezt, ked->f1); } return 0; } static short mirror_bezier_value(KeyframeEditData *ked, BezTriple *bezt) { /* value to mirror over is stored in the custom data -> first float value slot */ if (bezt->f2 & SELECT) { mirror_bezier_yaxis_ex(bezt, ked->f1); } return 0; } KeyframeEditFunc ANIM_editkeyframes_mirror(short mode) { switch (mode) { case MIRROR_KEYS_CURFRAME: /* mirror over current frame */ return mirror_bezier_cframe; case MIRROR_KEYS_YAXIS: /* mirror over frame 0 */ return mirror_bezier_yaxis; case MIRROR_KEYS_XAXIS: /* mirror over value 0 */ return mirror_bezier_xaxis; case MIRROR_KEYS_MARKER: /* mirror over marker */ return mirror_bezier_marker; case MIRROR_KEYS_TIME: /* mirror over frame/time */ return mirror_bezier_time; case MIRROR_KEYS_VALUE: /* mirror over given value */ return mirror_bezier_value; default: /* just in case */ return mirror_bezier_yaxis; } } /* ******************************************* */ /* Settings */ /** * Standard validation step for a few of these * (implemented as macro for inlining without fn-call overhead): * "if the handles are not of the same type, set them to type free". */ #define ENSURE_HANDLES_MATCH(bezt) \ if (bezt->h1 != bezt->h2) { \ if (ELEM(bezt->h1, HD_ALIGN, HD_AUTO, HD_AUTO_ANIM)) { \ bezt->h1 = HD_FREE; \ } \ if (ELEM(bezt->h2, HD_ALIGN, HD_AUTO, HD_AUTO_ANIM)) { \ bezt->h2 = HD_FREE; \ } \ } \ (void)0 /* Sets the selected bezier handles to type 'auto' */ static short set_bezier_auto(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* If the key is selected, always apply to both handles. */ if (bezt->f2 & SELECT) { bezt->h1 = bezt->h2 = HD_AUTO; } else { if (bezt->f1 & SELECT) { bezt->h1 = HD_AUTO; } if (bezt->f3 & SELECT) { bezt->h2 = HD_AUTO; } ENSURE_HANDLES_MATCH(bezt); } return 0; } /* Sets the selected bezier handles to type 'auto-clamped' * NOTE: this is like auto above, but they're handled a bit different */ static short set_bezier_auto_clamped(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* If the key is selected, always apply to both handles. */ if (bezt->f2 & SELECT) { bezt->h1 = bezt->h2 = HD_AUTO_ANIM; } else { if (bezt->f1 & SELECT) { bezt->h1 = HD_AUTO_ANIM; } if (bezt->f3 & SELECT) { bezt->h2 = HD_AUTO_ANIM; } ENSURE_HANDLES_MATCH(bezt); } return 0; } /* Sets the selected bezier handles to type 'vector'. */ static short set_bezier_vector(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* If the key is selected, always apply to both handles. */ if (bezt->f2 & SELECT) { bezt->h1 = bezt->h2 = HD_VECT; } else { if (bezt->f1 & SELECT) { bezt->h1 = HD_VECT; } if (bezt->f3 & SELECT) { bezt->h2 = HD_VECT; } } return 0; } /** * Queries if the handle should be set to 'free' or 'align'. * * \note This was used for the 'toggle free/align' option * currently this isn't used, but may be restored later. */ static short bezier_isfree(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if ((bezt->f1 & SELECT) && (bezt->h1)) { return 1; } if ((bezt->f3 & SELECT) && (bezt->h2)) { return 1; } return 0; } /* Sets selected bezier handles to type 'align' */ static short set_bezier_align(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* If the key is selected, always apply to both handles. */ if (bezt->f2 & SELECT) { bezt->h1 = bezt->h2 = HD_ALIGN; } else { if (bezt->f1 & SELECT) { bezt->h1 = HD_ALIGN; } if (bezt->f3 & SELECT) { bezt->h2 = HD_ALIGN; } } return 0; } /* Sets selected bezier handles to type 'free'. */ static short set_bezier_free(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* If the key is selected, always apply to both handles. */ if (bezt->f2 & SELECT) { bezt->h1 = bezt->h2 = HD_FREE; } else { if (bezt->f1 & SELECT) { bezt->h1 = HD_FREE; } if (bezt->f3 & SELECT) { bezt->h2 = HD_FREE; } } return 0; } KeyframeEditFunc ANIM_editkeyframes_handles(short mode) { switch (mode) { case HD_AUTO: /* auto */ return set_bezier_auto; case HD_AUTO_ANIM: /* auto clamped */ return set_bezier_auto_clamped; case HD_VECT: /* vector */ return set_bezier_vector; case HD_FREE: /* free */ return set_bezier_free; case HD_ALIGN: /* align */ return set_bezier_align; default: /* check for toggle free or align? */ return bezier_isfree; } } /* ------- */ static short set_bezt_constant(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_CONST; } return 0; } static short set_bezt_linear(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_LIN; } return 0; } static short set_bezt_bezier(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_BEZ; } return 0; } static short set_bezt_back(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_BACK; } return 0; } static short set_bezt_bounce(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_BOUNCE; } return 0; } static short set_bezt_circle(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_CIRC; } return 0; } static short set_bezt_cubic(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_CUBIC; } return 0; } static short set_bezt_elastic(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_ELASTIC; } return 0; } static short set_bezt_expo(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_EXPO; } return 0; } static short set_bezt_quad(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_QUAD; } return 0; } static short set_bezt_quart(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_QUART; } return 0; } static short set_bezt_quint(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_QUINT; } return 0; } static short set_bezt_sine(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->ipo = BEZT_IPO_SINE; } return 0; } static void handle_flatten(float vec[3][3], const int idx, const float direction[2]) { BLI_assert_msg(idx == 0 || idx == 2, "handle_flatten() expects a handle index"); add_v2_v2v2(vec[idx], vec[1], direction); } static void handle_set_length(float vec[3][3], const int idx, const float handle_length) { BLI_assert_msg(idx == 0 || idx == 2, "handle_set_length() expects a handle index"); float handle_direction[2]; sub_v2_v2v2(handle_direction, vec[idx], vec[1]); normalize_v2_length(handle_direction, handle_length); add_v2_v2v2(vec[idx], vec[1], handle_direction); } void ANIM_fcurve_equalize_keyframes_loop(FCurve *fcu, const eEditKeyframes_Equalize mode, const float handle_length, const bool flatten) { uint i; BezTriple *bezt; const float flat_direction_left[2] = {-handle_length, 0.0f}; const float flat_direction_right[2] = {handle_length, 0.0f}; /* Loop through an F-Curves keyframes. */ for (bezt = fcu->bezt, i = 0; i < fcu->totvert; bezt++, i++) { if ((bezt->f2 & SELECT) == 0) { continue; } /* Perform handle equalization if mode is 'Both' or 'Left'. */ if (mode & EQUALIZE_HANDLES_LEFT) { /* If left handle type is 'Auto', 'Auto Clamped', or 'Vector', convert handles to 'Aligned'. */ if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) { bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; } if (flatten) { handle_flatten(bezt->vec, 0, flat_direction_left); } else { handle_set_length(bezt->vec, 0, handle_length); } } /* Perform handle equalization if mode is 'Both' or 'Right'. */ if (mode & EQUALIZE_HANDLES_RIGHT) { /* If right handle type is 'Auto', 'Auto Clamped', or 'Vector', convert handles to * 'Aligned'. */ if (ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM, HD_VECT)) { bezt->h1 = HD_ALIGN; bezt->h2 = HD_ALIGN; } if (flatten) { handle_flatten(bezt->vec, 2, flat_direction_right); } else { handle_set_length(bezt->vec, 2, handle_length); } } } } KeyframeEditFunc ANIM_editkeyframes_ipo(short mode) { switch (mode) { /* interpolation */ case BEZT_IPO_CONST: /* constant */ return set_bezt_constant; case BEZT_IPO_LIN: /* linear */ return set_bezt_linear; /* easing */ case BEZT_IPO_BACK: return set_bezt_back; case BEZT_IPO_BOUNCE: return set_bezt_bounce; case BEZT_IPO_CIRC: return set_bezt_circle; case BEZT_IPO_CUBIC: return set_bezt_cubic; case BEZT_IPO_ELASTIC: return set_bezt_elastic; case BEZT_IPO_EXPO: return set_bezt_expo; case BEZT_IPO_QUAD: return set_bezt_quad; case BEZT_IPO_QUART: return set_bezt_quart; case BEZT_IPO_QUINT: return set_bezt_quint; case BEZT_IPO_SINE: return set_bezt_sine; default: /* bezier */ return set_bezt_bezier; } } /* ------- */ static short set_keytype_keyframe(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { BEZKEYTYPE(bezt) = BEZT_KEYTYPE_KEYFRAME; } return 0; } static short set_keytype_breakdown(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { BEZKEYTYPE(bezt) = BEZT_KEYTYPE_BREAKDOWN; } return 0; } static short set_keytype_extreme(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { BEZKEYTYPE(bezt) = BEZT_KEYTYPE_EXTREME; } return 0; } static short set_keytype_jitter(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { BEZKEYTYPE(bezt) = BEZT_KEYTYPE_JITTER; } return 0; } static short set_keytype_moving_hold(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { BEZKEYTYPE(bezt) = BEZT_KEYTYPE_MOVEHOLD; } return 0; } KeyframeEditFunc ANIM_editkeyframes_keytype(short mode) { switch (mode) { case BEZT_KEYTYPE_BREAKDOWN: /* breakdown */ return set_keytype_breakdown; case BEZT_KEYTYPE_EXTREME: /* extreme keyframe */ return set_keytype_extreme; case BEZT_KEYTYPE_JITTER: /* jitter keyframe */ return set_keytype_jitter; case BEZT_KEYTYPE_MOVEHOLD: /* moving hold */ return set_keytype_moving_hold; case BEZT_KEYTYPE_KEYFRAME: /* proper keyframe */ default: return set_keytype_keyframe; } } /* ------- */ static short set_easingtype_easein(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->easing = BEZT_IPO_EASE_IN; } return 0; } static short set_easingtype_easeout(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->easing = BEZT_IPO_EASE_OUT; } return 0; } static short set_easingtype_easeinout(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->easing = BEZT_IPO_EASE_IN_OUT; } return 0; } static short set_easingtype_easeauto(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { if (bezt->f2 & SELECT) { bezt->easing = BEZT_IPO_EASE_AUTO; } return 0; } KeyframeEditFunc ANIM_editkeyframes_easing(short mode) { switch (mode) { case BEZT_IPO_EASE_IN: /* ease in */ return set_easingtype_easein; case BEZT_IPO_EASE_OUT: /* ease out */ return set_easingtype_easeout; case BEZT_IPO_EASE_IN_OUT: /* both */ return set_easingtype_easeinout; default: /* auto */ return set_easingtype_easeauto; } } /* ******************************************* */ /* Selection */ static short select_bezier_add(KeyframeEditData *ked, BezTriple *bezt) { /* Only act on visible items, so check handle visibility state. */ const bool handles_visible = ked && ((ked->iterflags & KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE) ? BEZT_ISSEL_ANY(bezt) : true); /* if we've got info on what to select, use it, otherwise select all */ if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES) && handles_visible) { if (ked->curflags & KEYFRAME_OK_KEY) { bezt->f2 |= SELECT; } if (ked->curflags & KEYFRAME_OK_H1) { bezt->f1 |= SELECT; } if (ked->curflags & KEYFRAME_OK_H2) { bezt->f3 |= SELECT; } } else { BEZT_SEL_ALL(bezt); } return 0; } static short select_bezier_subtract(KeyframeEditData *ked, BezTriple *bezt) { /* Only act on visible items, so check handle visibility state. */ const bool handles_visible = ked && ((ked->iterflags & KEYFRAME_ITER_HANDLES_DEFAULT_INVISIBLE) ? BEZT_ISSEL_ANY(bezt) : true); /* if we've got info on what to deselect, use it, otherwise deselect all */ if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES) && handles_visible) { if (ked->curflags & KEYFRAME_OK_KEY) { bezt->f2 &= ~SELECT; } if (ked->curflags & KEYFRAME_OK_H1) { bezt->f1 &= ~SELECT; } if (ked->curflags & KEYFRAME_OK_H2) { bezt->f3 &= ~SELECT; } } else { BEZT_DESEL_ALL(bezt); } return 0; } static short select_bezier_invert(KeyframeEditData *UNUSED(ked), BezTriple *bezt) { /* Invert the selection for the whole bezier triple */ bezt->f2 ^= SELECT; if (bezt->f2 & SELECT) { bezt->f1 |= SELECT; bezt->f3 |= SELECT; } else { bezt->f1 &= ~SELECT; bezt->f3 &= ~SELECT; } return 0; } KeyframeEditFunc ANIM_editkeyframes_select(short selectmode) { switch (selectmode) { case SELECT_ADD: /* add */ return select_bezier_add; case SELECT_SUBTRACT: /* subtract */ return select_bezier_subtract; case SELECT_INVERT: /* invert */ return select_bezier_invert; default: /* replace (need to clear all, then add) */ return select_bezier_add; } } /* ******************************************* */ /* Selection Maps */ /* Selection maps are simply fancy names for char arrays that store on/off * info for whether the selection status. The main purpose for these is to * allow extra info to be tagged to the keyframes without influencing their * values or having to be removed later. */ /* ----------- */ static short selmap_build_bezier_more(KeyframeEditData *ked, BezTriple *bezt) { FCurve *fcu = ked->fcu; char *map = ked->data; int i = ked->curIndex; /* if current is selected, just make sure it stays this way */ if (BEZT_ISSEL_ANY(bezt)) { map[i] = 1; return 0; } /* if previous is selected, that means that selection should extend across */ if (i > 0) { BezTriple *prev = bezt - 1; if (BEZT_ISSEL_ANY(prev)) { map[i] = 1; return 0; } } /* if next is selected, that means that selection should extend across */ if (i < (fcu->totvert - 1)) { BezTriple *next = bezt + 1; if (BEZT_ISSEL_ANY(next)) { map[i] = 1; return 0; } } return 0; } static short selmap_build_bezier_less(KeyframeEditData *ked, BezTriple *bezt) { FCurve *fcu = ked->fcu; char *map = ked->data; int i = ked->curIndex; /* if current is selected, check the left/right keyframes * since it might need to be deselected (but otherwise no) */ if (BEZT_ISSEL_ANY(bezt)) { /* if previous is not selected, we're on the tip of an iceberg */ if (i > 0) { BezTriple *prev = bezt - 1; if (BEZT_ISSEL_ANY(prev) == 0) { return 0; } } else if (i == 0) { /* current keyframe is selected at an endpoint, so should get deselected */ return 0; } /* if next is not selected, we're on the tip of an iceberg */ if (i < (fcu->totvert - 1)) { BezTriple *next = bezt + 1; if (BEZT_ISSEL_ANY(next) == 0) { return 0; } } else if (i == (fcu->totvert - 1)) { /* current keyframe is selected at an endpoint, so should get deselected */ return 0; } /* if we're still here, that means that keyframe should remain untouched */ map[i] = 1; } return 0; } KeyframeEditFunc ANIM_editkeyframes_buildselmap(short mode) { switch (mode) { case SELMAP_LESS: /* less */ return selmap_build_bezier_less; case SELMAP_MORE: /* more */ default: return selmap_build_bezier_more; } } /* ----------- */ short bezt_selmap_flush(KeyframeEditData *ked, BezTriple *bezt) { const char *map = ked->data; short on = map[ked->curIndex]; /* select or deselect based on whether the map allows it or not */ if (on) { BEZT_SEL_ALL(bezt); } else { BEZT_DESEL_ALL(bezt); } return 0; }