Make auto handle placement aware of cyclic extrapolation.
authorAlexander Gavrilov <angavrilov@gmail.com>
Tue, 17 Oct 2017 16:39:10 +0000 (19:39 +0300)
committerAlexander Gavrilov <angavrilov@gmail.com>
Tue, 17 Oct 2017 16:39:10 +0000 (19:39 +0300)
Cyclic extrapolation is implemented as an f-curve modifier, so this
technically violates abstraction separation and is something of a hack.
However without such behavior achieving smooth looping with cyclic
extrapolation is extremely cumbersome.

The new behavior is applied when the first modifier is Cyclic
extrapolation in Repeat or Repeat with Offset mode without
using influence, repeat count or range restrictions.

This change in behavior means that curve handles have to be updated
when the modifier is added, removed or its options change. Due to the
way code is structured, it seems it requires a helper link to the
containing curve from the modifier object.

Reviewers: aligorith

Differential Revision: https://developer.blender.org/D2783

16 files changed:
source/blender/blenkernel/BKE_fcurve.h
source/blender/blenkernel/intern/fcurve.c
source/blender/blenkernel/intern/fmodifier.c
source/blender/blenkernel/intern/ipo.c
source/blender/blenloader/intern/readfile.c
source/blender/editors/animation/drivers.c
source/blender/editors/animation/fmodifier_ui.c
source/blender/editors/include/ED_anim_api.h
source/blender/editors/object/object_constraint.c
source/blender/editors/object/object_relations.c
source/blender/editors/space_action/action_edit.c
source/blender/editors/space_graph/graph_edit.c
source/blender/editors/space_nla/nla_edit.c
source/blender/makesdna/DNA_anim_types.h
source/blender/makesdna/DNA_curve_types.h
source/blender/makesrna/intern/rna_fcurve.c

index b38f129976396e959f91cd97e099f6e75efa104a..09f5ecce0504560dfaf4cf90633485d5fe2a6f1b 100644 (file)
@@ -188,7 +188,7 @@ const FModifierTypeInfo *get_fmodifier_typeinfo(const int type);
 
 /* ---------------------- */
 
-struct FModifier *add_fmodifier(ListBase *modifiers, int type);
+struct FModifier *add_fmodifier(ListBase *modifiers, int type, struct FCurve *owner_fcu);
 struct FModifier *copy_fmodifier(const struct FModifier *src);
 void copy_fmodifiers(ListBase *dst, const ListBase *src);
 bool remove_fmodifier(ListBase *modifiers, struct FModifier *fcm);
@@ -266,6 +266,9 @@ bool fcurve_are_keyframes_usable(struct FCurve *fcu);
 bool fcurve_is_keyframable(struct FCurve *fcu);
 bool BKE_fcurve_is_protected(struct FCurve *fcu);
 
+/* The curve is an infinite cycle via Cycles modifier */
+bool BKE_fcurve_is_cyclic(struct FCurve *fcu);
+
 /* -------- Curve Sanity --------  */
 
 void calchandles_fcurve(struct FCurve *fcu);
index aee465ad0a0114100cffe33247502f455303a855..103f23a2c186de2456c626b35032f02fd8fe1f30 100644 (file)
@@ -881,6 +881,46 @@ void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSample
  * that the handles are correctly 
  */
 
+/* Checks if the F-Curve has a Cycles modifier with simple settings that warrant transition smoothing */
+bool BKE_fcurve_is_cyclic(FCurve *fcu)
+{
+       FModifier *fcm = fcu->modifiers.first;
+
+       if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES)
+               return false;
+
+       if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED))
+               return false;
+
+       if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE))
+               return false;
+
+       FMod_Cycles *data = (FMod_Cycles*)fcm->data;
+
+       return data && data->after_cycles == 0 && data->before_cycles == 0 &&
+           ELEM(data->before_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET) &&
+           ELEM(data->after_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET);
+}
+
+/* Shifts 'in' by the difference in coordinates between 'to' and 'from', using 'out' as the output buffer.
+ * When 'to' and 'from' are end points of the loop, this moves the 'in' point one loop cycle.
+ */
+static BezTriple *cycle_offset_triple(bool cycle, BezTriple *out, const BezTriple *in, const BezTriple *from, const BezTriple *to)
+{
+       if (!cycle)
+               return NULL;
+
+       memcpy(out, in, sizeof(BezTriple));
+
+       float delta[3];
+       sub_v3_v3v3(delta, to->vec[1], from->vec[1]);
+
+       for (int i = 0; i < 3; i++)
+               add_v3_v3(out->vec[i], delta);
+
+       return out;
+}
+
 /* This function recalculates the handles of an F-Curve 
  * If the BezTriples have been rearranged, sort them first before using this.
  */
@@ -896,10 +936,16 @@ void calchandles_fcurve(FCurve *fcu)
         */
        if (ELEM(NULL, fcu, fcu->bezt) || (a < 2) /*|| ELEM(fcu->ipo, BEZT_IPO_CONST, BEZT_IPO_LIN)*/) 
                return;
-       
+
+       /* if the first modifier is Cycles, smooth the curve through the cycle */
+       BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert-1];
+       BezTriple tmp;
+
+       bool cycle = BKE_fcurve_is_cyclic(fcu) && BEZT_IS_AUTOH(first) && BEZT_IS_AUTOH(last);
+
        /* get initial pointers */
        bezt = fcu->bezt;
-       prev = NULL;
+       prev = cycle_offset_triple(cycle, &tmp, &fcu->bezt[fcu->totvert-2], last, first);
        next = (bezt + 1);
        
        /* loop over all beztriples, adjusting handles */
@@ -912,7 +958,7 @@ void calchandles_fcurve(FCurve *fcu)
                BKE_nurb_handle_calc(bezt, prev, next, true);
                
                /* for automatic ease in and out */
-               if (ELEM(bezt->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM(bezt->h2, HD_AUTO, HD_AUTO_ANIM)) {
+               if (BEZT_IS_AUTOH(bezt) && !cycle) {
                        /* only do this on first or last beztriple */
                        if ((a == 0) || (a == fcu->totvert - 1)) {
                                /* set both handles to have same horizontal value as keyframe */
@@ -924,8 +970,14 @@ void calchandles_fcurve(FCurve *fcu)
                
                /* advance pointers for next iteration */
                prev = bezt;
-               if (a == 1) next = NULL;
-               else next++;
+
+               if (a == 1) {
+                       next = cycle_offset_triple(cycle, &tmp, &fcu->bezt[1], first, last);
+               }
+               else {
+                       next++;
+               }
+
                bezt++;
        }
 }
index f1732ee7a9a1a0c914d61fef956836dafc3bd322..f1834bdf8a662074e1d50477744dfdefdaa8d095 100644 (file)
@@ -1077,7 +1077,7 @@ const FModifierTypeInfo *fmodifier_get_typeinfo(const FModifier *fcm)
 /* API --------------------------- */
 
 /* Add a new F-Curve Modifier to the given F-Curve of a certain type */
-FModifier *add_fmodifier(ListBase *modifiers, int type)
+FModifier *add_fmodifier(ListBase *modifiers, int type, FCurve *owner_fcu)
 {
        const FModifierTypeInfo *fmi = get_fmodifier_typeinfo(type);
        FModifier *fcm;
@@ -1098,6 +1098,7 @@ FModifier *add_fmodifier(ListBase *modifiers, int type)
        fcm = MEM_callocN(sizeof(FModifier), "F-Curve Modifier");
        fcm->type = type;
        fcm->flag = FMODIFIER_FLAG_EXPANDED;
+       fcm->curve = owner_fcu;
        fcm->influence = 1.0f;
        BLI_addtail(modifiers, fcm);
        
@@ -1111,6 +1112,10 @@ FModifier *add_fmodifier(ListBase *modifiers, int type)
        /* init custom settings if necessary */
        if (fmi->new_data)
                fmi->new_data(fcm->data);
+
+       /* update the fcurve if the Cycles modifier is added */
+       if ((owner_fcu) && (type == FMODIFIER_TYPE_CYCLES))
+               calchandles_fcurve(owner_fcu);
                
        /* return modifier for further editing */
        return fcm;
@@ -1129,6 +1134,7 @@ FModifier *copy_fmodifier(const FModifier *src)
        /* copy the base data, clearing the links */
        dst = MEM_dupallocN(src);
        dst->next = dst->prev = NULL;
+       dst->curve = NULL;
        
        /* make a new copy of the F-Modifier's data */
        dst->data = MEM_dupallocN(src->data);
@@ -1157,6 +1163,7 @@ void copy_fmodifiers(ListBase *dst, const ListBase *src)
                
                /* make a new copy of the F-Modifier's data */
                fcm->data = MEM_dupallocN(fcm->data);
+               fcm->curve = NULL;
                
                /* only do specific constraints if required */
                if (fmi && fmi->copy_data)
@@ -1173,6 +1180,9 @@ bool remove_fmodifier(ListBase *modifiers, FModifier *fcm)
        if (fcm == NULL)
                return false;
        
+       /* removing the cycles modifier requires a handle update */
+       FCurve *update_fcu = (fcm->type == FMODIFIER_TYPE_CYCLES) ? fcm->curve : NULL;
+
        /* free modifier's special data (stored inside fcm->data) */
        if (fcm->data) {
                if (fmi && fmi->free_data)
@@ -1185,6 +1195,11 @@ bool remove_fmodifier(ListBase *modifiers, FModifier *fcm)
        /* remove modifier from stack */
        if (modifiers) {
                BLI_freelinkN(modifiers, fcm);
+
+               /* update the fcurve if the Cycles modifier is removed */
+               if (update_fcu)
+                       calchandles_fcurve(update_fcu);
+
                return true;
        }
        else {
index f3a85dcee2be97049f78db5ddb2e19ac19e9a14c..90247441631ba80d294847290d3573fe4bb9dfb5 100644 (file)
@@ -1192,7 +1192,7 @@ static void icu_to_fcurves(ID *id, ListBase *groups, ListBase *list, IpoCurve *i
                        /* Add a new FModifier (Cyclic) instead of setting extend value 
                         * as that's the new equivalent of that option.
                         */
-                       FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
+                       FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu);
                        FMod_Cycles *data = (FMod_Cycles *)fcm->data;
                        
                        /* if 'offset' one is in use, set appropriate settings */
index 3b7662be2b2d09338ad8fac6c38252622b7b76bf..231b467a0af5dba82324514f7503fb552db4bfea 100644 (file)
@@ -2441,13 +2441,14 @@ static void lib_link_fcurves(FileData *fd, ID *id, ListBase *list)
 
 
 /* NOTE: this assumes that link_list has already been called on the list */
-static void direct_link_fmodifiers(FileData *fd, ListBase *list)
+static void direct_link_fmodifiers(FileData *fd, ListBase *list, FCurve *curve)
 {
        FModifier *fcm;
        
        for (fcm = list->first; fcm; fcm = fcm->next) {
                /* relink general data */
                fcm->data  = newdataadr(fd, fcm->data);
+               fcm->curve = curve;
                
                /* do relinking of data for specific types */
                switch (fcm->type) {
@@ -2537,7 +2538,7 @@ static void direct_link_fcurves(FileData *fd, ListBase *list)
                
                /* modifiers */
                link_list(fd, &fcu->modifiers);
-               direct_link_fmodifiers(fd, &fcu->modifiers);
+               direct_link_fmodifiers(fd, &fcu->modifiers, fcu);
        }
 }
 
@@ -2642,7 +2643,7 @@ static void direct_link_nladata_strips(FileData *fd, ListBase *list)
                
                /* strip's F-Modifiers */
                link_list(fd, &strip->modifiers);
-               direct_link_fmodifiers(fd, &strip->modifiers);
+               direct_link_fmodifiers(fd, &strip->modifiers, NULL);
        }
 }
 
index 52b93edae15d0bb3a1f364822e67ffb2bae2f61d..0d329898c4186112609fb03baeaebde7b7d2b8f2 100644 (file)
@@ -123,7 +123,7 @@ FCurve *verify_driver_fcurve(ID *id, const char rna_path[], const int array_inde
                                 * Create FModifier so that old scripts won't break
                                 * for now before 2.7 series -- (September 4, 2013)
                                 */
-                               add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR);
+                               add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
                        }
                        else {
                                /* add 2 keyframes so that user has something to work with 
index 6bb73416fecd8a0876803078c728ab5877badd5a..8d77460e19701f693f528ae70542d739b28a0221 100644 (file)
@@ -736,7 +736,7 @@ bool ANIM_fmodifiers_copy_to_buf(ListBase *modifiers, bool active)
 /* 'Paste' the F-Modifier(s) from the buffer to the specified list 
  *     - replace: free all the existing modifiers to leave only the pasted ones 
  */
-bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace)
+bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace, FCurve *curve)
 {
        FModifier *fcm;
        bool ok = false;
@@ -745,6 +745,8 @@ bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace)
        if (modifiers == NULL)
                return 0;
                
+       bool was_cyclic = curve && BKE_fcurve_is_cyclic(curve);
+
        /* if replacing the list, free the existing modifiers */
        if (replace)
                free_fmodifiers(modifiers);
@@ -753,6 +755,8 @@ bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace)
        for (fcm = fmodifier_copypaste_buf.first; fcm; fcm = fcm->next) {
                /* make a copy of it */
                FModifier *fcmN = copy_fmodifier(fcm);
+
+               fcmN->curve = curve;
                
                /* make sure the new one isn't active, otherwise the list may get several actives */
                fcmN->flag &= ~FMODIFIER_FLAG_ACTIVE;
@@ -762,6 +766,10 @@ bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace)
                ok = 1;
        }
        
+       /* adding or removing the Cycles modifier requires an update to handles */
+       if (curve && BKE_fcurve_is_cyclic(curve) != was_cyclic)
+               calchandles_fcurve(curve);
+
        /* did we succeed? */
        return ok;
 }
index 68a5dd8a94e472c2e495fb90ef60901b5a1d1cb4..ab02fd1ccc86ac11ad84adf56a9b8eafbcda9705 100644 (file)
@@ -572,7 +572,7 @@ bool ANIM_fmodifiers_copy_to_buf(ListBase *modifiers, bool active);
 /* 'Paste' the F-Modifier(s) from the buffer to the specified list 
  *     - replace: free all the existing modifiers to leave only the pasted ones 
  */
-bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace);
+bool ANIM_fmodifiers_paste_from_buf(ListBase *modifiers, bool replace, struct FCurve *curve);
 
 /* ************************************************* */
 /* ASSORTED TOOLS */
index 59d78f13ccb41a46760583361b75ae309fd43d96..f348806c6f1db080beb56ea7e9d664f78003c8b1 100644 (file)
@@ -1025,7 +1025,7 @@ static int followpath_path_animate_exec(bContext *C, wmOperator *op)
         * and define basic slope of this curve based on the properties
         */
        if (!fcu->bezt && !fcu->fpt && !fcu->modifiers.first) {
-               FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR);
+               FModifier *fcm = add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
                FMod_Generator *gen = fcm->data;
                
                /* Assume that we have the following equation:
index d5b516257a17d9d434ffd24165bd0ce67cd4a844..91fdfcacf6cb03f5afb736991428a33ad5f66c74 100644 (file)
@@ -637,7 +637,7 @@ bool ED_object_parent_set(ReportList *reports, Main *bmain, Scene *scene, Object
 
                                /* setup dummy 'generator' modifier here to get 1-1 correspondence still working */
                                if (!fcu->bezt && !fcu->fpt && !fcu->modifiers.first)
-                                       add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR);
+                                       add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_GENERATOR, fcu);
                        }
 
                        /* fall back on regular parenting now (for follow only) */
index a9920389980e9f19c5694ceee87cd75045813a5e..2df5aa29e9a1d4b4505173fab163d8525b542a4c 100644 (file)
@@ -1146,7 +1146,7 @@ static void setexpo_action_keys(bAnimContext *ac, short mode)
                                /* only add if one doesn't exist */
                                if (list_has_suitable_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, -1) == 0) {
                                        /* TODO: add some more preset versions which set different extrapolation options? */
-                                       add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
+                                       add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu);
                                }
                        }
                        else if (mode == CLEAR_CYCLIC_EXPO) {
index 861a38da2c7294f845495e68d6b281e9ddb45f55..4f737ef164b393ab7957c103f4f1aeec6a07bcb9 100644 (file)
@@ -1504,7 +1504,7 @@ static void setexpo_graph_keys(bAnimContext *ac, short mode)
                                /* only add if one doesn't exist */
                                if (list_has_suitable_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, -1) == 0) {
                                        // TODO: add some more preset versions which set different extrapolation options?
-                                       add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES);
+                                       add_fmodifier(&fcu->modifiers, FMODIFIER_TYPE_CYCLES, fcu);
                                }
                        }
                        else if (mode == CLEAR_CYCLIC_EXPO) {
@@ -2446,7 +2446,7 @@ static int graph_fmodifier_add_exec(bContext *C, wmOperator *op)
                FModifier *fcm;
                
                /* add F-Modifier of specified type to active F-Curve, and make it the active one */
-               fcm = add_fmodifier(&fcu->modifiers, type);
+               fcm = add_fmodifier(&fcu->modifiers, type, fcu);
                if (fcm) {
                        set_active_fmodifier(&fcu->modifiers, fcm);
                }
@@ -2582,7 +2582,7 @@ static int graph_fmodifier_paste_exec(bContext *C, wmOperator *op)
                FCurve *fcu = (FCurve *)ale->data;
                int tot;
                
-               tot = ANIM_fmodifiers_paste_from_buf(&fcu->modifiers, replace);
+               tot = ANIM_fmodifiers_paste_from_buf(&fcu->modifiers, replace, fcu);
                
                if (tot) {
                        ale->update |= ANIM_UPDATE_DEPS;
index f7f7c82171d422a9a2b77d0f38c447a6e68d2fdc..a6f76e1d5565aa6894e98432e5d9a02918c22b20 100644 (file)
@@ -2316,7 +2316,7 @@ static int nla_fmodifier_add_exec(bContext *C, wmOperator *op)
                                continue;
                        
                        /* add F-Modifier of specified type to selected, and make it the active one */
-                       fcm = add_fmodifier(&strip->modifiers, type);
+                       fcm = add_fmodifier(&strip->modifiers, type, NULL);
                        
                        if (fcm) {
                                set_active_fmodifier(&strip->modifiers, fcm);
@@ -2470,7 +2470,7 @@ static int nla_fmodifier_paste_exec(bContext *C, wmOperator *op)
                        }
                        
                        /* paste FModifiers from buffer */
-                       ok += ANIM_fmodifiers_paste_from_buf(&strip->modifiers, replace);
+                       ok += ANIM_fmodifiers_paste_from_buf(&strip->modifiers, replace, NULL);
                        ale->update |= ANIM_UPDATE_DEPS;
                }
        }
index 935a893f689dfa848671c1138e948fabac4e982b..977cd2347ad6bfbd8f47ee7a321af65a949b469e 100644 (file)
@@ -52,6 +52,7 @@ extern "C" {
 typedef struct FModifier {
        struct FModifier *next, *prev;
        
+       struct FCurve *curve;  /* containing curve, only used for updates to CYCLES */
        void *data;                     /* pointer to modifier data */
        
        char name[64];          /* user-defined description for the modifier - MAX_ID_NAME-2 */
index 902fa4ce9879d8e11157c209cfe33ac71513fc44..16647e78eff498166d68063048a99533e227d293 100644 (file)
@@ -436,6 +436,8 @@ typedef enum eBezTriple_KeyframeType {
 #define BEZT_SEL_ALL(bezt)    { (bezt)->f1 |=  SELECT; (bezt)->f2 |=  SELECT; (bezt)->f3 |=  SELECT; } ((void)0)
 #define BEZT_DESEL_ALL(bezt)  { (bezt)->f1 &= ~SELECT; (bezt)->f2 &= ~SELECT; (bezt)->f3 &= ~SELECT; } ((void)0)
 
+#define BEZT_IS_AUTOH(bezt)   (ELEM((bezt)->h1, HD_AUTO, HD_AUTO_ANIM) && ELEM((bezt)->h2, HD_AUTO, HD_AUTO_ANIM))
+
 /* *************** CHARINFO **************** */
 
 /* CharInfo.flag */
index ad63a652b120c760cffd578a8f4ca140705fd0fb..f7da5a0fda47a807ab9d2d819c929b363e720fea 100644 (file)
@@ -495,7 +495,7 @@ static void rna_FCurve_active_modifier_set(PointerRNA *ptr, PointerRNA value)
 
 static FModifier *rna_FCurve_modifiers_new(FCurve *fcu, int type)
 {
-       return add_fmodifier(&fcu->modifiers, type);
+       return add_fmodifier(&fcu->modifiers, type, fcu);
 }
 
 static void rna_FCurve_modifiers_remove(FCurve *fcu, ReportList *reports, PointerRNA *fcm_ptr)
@@ -586,11 +586,15 @@ static void rna_FModifier_blending_range(PointerRNA *ptr, float *min, float *max
 static void rna_FModifier_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
 {
        ID *id = ptr->id.data;
+       FModifier *fcm = (FModifier *)ptr->data;
        AnimData *adt = BKE_animdata_from_id(id);
        DAG_id_tag_update(id, (GS(id->name) == ID_OB) ? OB_RECALC_OB : OB_RECALC_DATA);
        if (adt != NULL) {
                adt->recalc |= ADT_RECALC_ANIM;
        }
+       if (fcm->curve && fcm->type == FMODIFIER_TYPE_CYCLES) {
+               calchandles_fcurve(fcm->curve);
+       }
 }
 
 static void rna_FModifier_verify_data_update(Main *bmain, Scene *scene, PointerRNA *ptr)