diff options
Diffstat (limited to 'source/blender/editors/animation/keyframes_general.c')
-rw-r--r-- | source/blender/editors/animation/keyframes_general.c | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/source/blender/editors/animation/keyframes_general.c b/source/blender/editors/animation/keyframes_general.c new file mode 100644 index 00000000000..48ca06fb73d --- /dev/null +++ b/source/blender/editors/animation/keyframes_general.c @@ -0,0 +1,548 @@ +/** + * $Id: + * + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * The Original Code is Copyright (C) 2008 Blender Foundation + * All rights reserved. + * + * Contributor(s): Joshua Leung + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <float.h> + +#include "MEM_guardedalloc.h" + +#include "BLI_blenlib.h" +#include "BLI_arithb.h" + +#include "DNA_anim_types.h" +#include "DNA_action_types.h" +#include "DNA_curve_types.h" +#include "DNA_key_types.h" +#include "DNA_object_types.h" +#include "DNA_space_types.h" +#include "DNA_scene_types.h" + +#include "BKE_action.h" +#include "BKE_fcurve.h" +#include "BKE_key.h" +#include "BKE_utildefines.h" + +#include "ED_anim_api.h" +#include "ED_keyframing.h" +#include "ED_keyframes_edit.h" + +/* This file contains code for various keyframe-editing tools which are 'destructive' + * (i.e. they will modify the order of the keyframes, and change the size of the array). + * While some of these tools may eventually be moved out into blenkernel, for now, it is + * fine to have these calls here. + * + * There are also a few tools here which cannot be easily coded for in the other system (yet). + * These may also be moved around at some point, but for now, they + * + * - Joshua Leung, Dec 2008 + */ + +/* **************************************************** */ + +/* Only delete the nominated keyframe from provided ipo-curve. + * Not recommended to be used many times successively. For that + * there is delete_ipo_keys(). + */ +void delete_fcurve_key(FCurve *fcu, int index, short do_recalc) +{ + /* firstly check that index is valid */ + if (index < 0) + index *= -1; + if (fcu == NULL) + return; + if (index >= fcu->totvert) + return; + + /* Delete this key */ + memmove(&fcu->bezt[index], &fcu->bezt[index+1], sizeof(BezTriple)*(fcu->totvert-index-1)); + fcu->totvert--; + + /* recalc handles - only if it won't cause problems */ + if (do_recalc) + calchandles_fcurve(fcu); +} + +/* Delete selected keyframes in given F-Curve */ +void delete_fcurve_keys(FCurve *fcu) +{ + int i; + + /* Delete selected BezTriples */ + for (i=0; i < fcu->totvert; i++) { + if (fcu->bezt[i].f2 & SELECT) { + memmove(&fcu->bezt[i], &fcu->bezt[i+1], sizeof(BezTriple)*(fcu->totvert-i-1)); + fcu->totvert--; + i--; + } + } + + /* Free the array of BezTriples if there are not keyframes */ + if (fcu->totvert == 0) { + if (fcu->bezt) + MEM_freeN(fcu->bezt); + fcu->bezt= NULL; + } + +#if 0 // XXX for now, we don't get rid of empty curves... + /* Only delete if there isn't an ipo-driver still hanging around on an empty curve */ + if ((icu->totvert==0) && (icu->driver==NULL)) { + BLI_remlink(&ipo->curve, icu); + free_ipo_curve(icu); + } +#endif +} + +/* ---------------- */ + +/* duplicate selected keyframes for the given F-Curve */ +void duplicate_fcurve_keys(FCurve *fcu) +{ + BezTriple *newbezt; + int i; + + if (fcu == NULL) + return; + + // XXX this does not take into account sample data... + for (i=0; i < fcu->totvert; i++) { + /* If a key is selected */ + if (fcu->bezt[i].f2 & SELECT) { + /* Expand the list */ + newbezt = MEM_callocN(sizeof(BezTriple) * (fcu->totvert+1), "beztriple"); + + memcpy(newbezt, fcu->bezt, sizeof(BezTriple) * (i+1)); + memcpy(newbezt+i+1, fcu->bezt+i, sizeof(BezTriple)); + memcpy(newbezt+i+2, fcu->bezt+i+1, sizeof (BezTriple) *(fcu->totvert-(i+1))); + fcu->totvert++; + + /* reassign pointers... (free old, and add new) */ + MEM_freeN(fcu->bezt); + fcu->bezt=newbezt; + + /* Unselect the current key */ + BEZ_DESEL(&fcu->bezt[i]); + i++; + + /* Select the copied key */ + BEZ_SEL(&fcu->bezt[i]); + } + } +} + +/* **************************************************** */ +/* Various Tools */ + +/* Basic IPO-Curve 'cleanup' function that removes 'double points' and unnecessary keyframes on linear-segments only */ +void clean_fcurve(FCurve *fcu, float thresh) +{ + BezTriple *old_bezts, *bezt, *beztn; + BezTriple *lastb; + int totCount, i; + + /* check if any points */ + if ((fcu == NULL) || (fcu->totvert <= 1)) + return; + + /* make a copy of the old BezTriples, and clear IPO curve */ + old_bezts = fcu->bezt; + totCount = fcu->totvert; + fcu->bezt = NULL; + fcu->totvert = 0; + + /* now insert first keyframe, as it should be ok */ + bezt = old_bezts; + insert_vert_fcurve(fcu, bezt->vec[1][0], bezt->vec[1][1], 0); + + /* Loop through BezTriples, comparing them. Skip any that do + * not fit the criteria for "ok" points. + */ + for (i=1; i<totCount; i++) { + float prev[2], cur[2], next[2]; + + /* get BezTriples and their values */ + if (i < (totCount - 1)) { + beztn = (old_bezts + (i+1)); + next[0]= beztn->vec[1][0]; next[1]= beztn->vec[1][1]; + } + else { + beztn = NULL; + next[0] = next[1] = 0.0f; + } + lastb= (fcu->bezt + (fcu->totvert - 1)); + bezt= (old_bezts + i); + + /* get references for quicker access */ + prev[0] = lastb->vec[1][0]; prev[1] = lastb->vec[1][1]; + cur[0] = bezt->vec[1][0]; cur[1] = bezt->vec[1][1]; + + /* check if current bezt occurs at same time as last ok */ + if (IS_EQT(cur[0], prev[0], thresh)) { + /* If there is a next beztriple, and if occurs at the same time, only insert + * if there is a considerable distance between the points, and also if the + * current is further away than the next one is to the previous. + */ + if (beztn && (IS_EQT(cur[0], next[0], thresh)) && + (IS_EQT(next[1], prev[1], thresh)==0)) + { + /* only add if current is further away from previous */ + if (cur[1] > next[1]) { + if (IS_EQT(cur[1], prev[1], thresh) == 0) { + /* add new keyframe */ + insert_vert_fcurve(fcu, cur[0], cur[1], 0); + } + } + } + else { + /* only add if values are a considerable distance apart */ + if (IS_EQT(cur[1], prev[1], thresh) == 0) { + /* add new keyframe */ + insert_vert_fcurve(fcu, cur[0], cur[1], 0); + } + } + } + else { + /* checks required are dependent on whether this is last keyframe or not */ + if (beztn) { + /* does current have same value as previous and next? */ + if (IS_EQT(cur[1], prev[1], thresh) == 0) { + /* add new keyframe*/ + insert_vert_fcurve(fcu, cur[0], cur[1], 0); + } + else if (IS_EQT(cur[1], next[1], thresh) == 0) { + /* add new keyframe */ + insert_vert_fcurve(fcu, cur[0], cur[1], 0); + } + } + else { + /* add if value doesn't equal that of previous */ + if (IS_EQT(cur[1], prev[1], thresh) == 0) { + /* add new keyframe */ + insert_vert_fcurve(fcu, cur[0], cur[1], 0); + } + } + } + } + + /* now free the memory used by the old BezTriples */ + if (old_bezts) + MEM_freeN(old_bezts); +} + +/* ---------------- */ + +/* temp struct used for smooth_ipo */ +typedef struct tSmooth_Bezt { + float *h1, *h2, *h3; /* bezt->vec[0,1,2][1] */ +} tSmooth_Bezt; + +/* Use a weighted moving-means method to reduce intensity of fluctuations */ +void smooth_fcurve (FCurve *fcu) +{ + BezTriple *bezt; + int i, x, totSel = 0; + + /* first loop through - count how many verts are selected, and fix up handles + * this is done for both modes + */ + bezt= fcu->bezt; + for (i=0; i < fcu->totvert; i++, bezt++) { + if (BEZSELECTED(bezt)) { + /* line point's handles up with point's vertical position */ + bezt->vec[0][1]= bezt->vec[2][1]= bezt->vec[1][1]; + if ((bezt->h1==HD_AUTO) || (bezt->h1==HD_VECT)) bezt->h1= HD_ALIGN; + if ((bezt->h2==HD_AUTO) || (bezt->h2==HD_VECT)) bezt->h2= HD_ALIGN; + + /* add value to total */ + totSel++; + } + } + + /* if any points were selected, allocate tSmooth_Bezt points to work on */ + if (totSel >= 3) { + tSmooth_Bezt *tarray, *tsb; + + /* allocate memory in one go */ + tsb= tarray= MEM_callocN(totSel*sizeof(tSmooth_Bezt), "tSmooth_Bezt Array"); + + /* populate tarray with data of selected points */ + bezt= fcu->bezt; + for (i=0, x=0; (i < fcu->totvert) && (x < totSel); i++, bezt++) { + if (BEZSELECTED(bezt)) { + /* tsb simply needs pointer to vec, and index */ + tsb->h1 = &bezt->vec[0][1]; + tsb->h2 = &bezt->vec[1][1]; + tsb->h3 = &bezt->vec[2][1]; + + /* advance to the next tsb to populate */ + if (x < totSel- 1) + tsb++; + else + break; + } + } + + /* calculate the new smoothed F-Curve's with weighted averages: + * - this is done with two passes + * - uses 5 points for each operation (which stores in the relevant handles) + * - previous: w/a ratio = 3:5:2:1:1 + * - next: w/a ratio = 1:1:2:5:3 + */ + + /* round 1: calculate previous and next */ + tsb= tarray; + for (i=0; i < totSel; i++, tsb++) { + /* don't touch end points (otherwise, curves slowly explode) */ + if (ELEM(i, 0, (totSel-1)) == 0) { + const tSmooth_Bezt *tP1 = tsb - 1; + const tSmooth_Bezt *tP2 = (i-2 > 0) ? (tsb - 2) : (NULL); + const tSmooth_Bezt *tN1 = tsb + 1; + const tSmooth_Bezt *tN2 = (i+2 < totSel) ? (tsb + 2) : (NULL); + + const float p1 = *tP1->h2; + const float p2 = (tP2) ? (*tP2->h2) : (*tP1->h2); + const float c1 = *tsb->h2; + const float n1 = *tN1->h2; + const float n2 = (tN2) ? (*tN2->h2) : (*tN1->h2); + + /* calculate previous and next */ + *tsb->h1= (3*p2 + 5*p1 + 2*c1 + n1 + n2) / 12; + *tsb->h3= (p2 + p1 + 2*c1 + 5*n1 + 3*n2) / 12; + } + } + + /* round 2: calculate new values and reset handles */ + tsb= tarray; + for (i=0; i < totSel; i++, tsb++) { + /* calculate new position by averaging handles */ + *tsb->h2 = (*tsb->h1 + *tsb->h3) / 2; + + /* reset handles now */ + *tsb->h1 = *tsb->h2; + *tsb->h3 = *tsb->h2; + } + + /* free memory required for tarray */ + MEM_freeN(tarray); +} + + /* recalculate handles */ + calchandles_fcurve(fcu); +} + +/* **************************************************** */ +/* Copy/Paste Tools */ +/* - The copy/paste buffer currently stores a set of temporary F-Curves containing only the keyframes + * that were selected in each of the original F-Curves + * - All pasted frames are offset by the same amount. This is calculated as the difference in the times of + * the current frame and the 'first keyframe' (i.e. the earliest one in all channels). + * - The earliest frame is calculated per copy operation. + */ + +/* globals for copy/paste data (like for other copy/paste buffers) */ +ListBase animcopybuf = {NULL, NULL}; +static float animcopy_firstframe= 999999999.0f; + +/* datatype for use in copy/paste buffer */ +// XXX F-Curve editor should use this too +typedef struct tAnimCopybufItem { + struct tAnimCopybufItem *next, *prev; + + ID *id; /* ID which owns the curve */ + bActionGroup *grp; /* Action Group */ + char *rna_path; /* RNA-Path */ + int array_index; /* array index */ + + int totvert; /* number of keyframes stored for this channel */ + BezTriple *bezt; /* keyframes in buffer */ +} tAnimCopybufItem; + + +/* This function frees any MEM_calloc'ed copy/paste buffer data */ +// XXX find some header to put this in! +void free_anim_copybuf (void) +{ + tAnimCopybufItem *aci, *acn; + + /* free each buffer element */ + for (aci= animcopybuf.first; aci; aci= acn) { + acn= aci->next; + + /* free keyframes */ + if (aci->bezt) + MEM_freeN(aci->bezt); + + /* free RNA-path */ + if (aci->rna_path) + MEM_freeN(aci->rna_path); + + /* free ourself */ + BLI_freelinkN(&animcopybuf, aci); + } + + /* restore initial state */ + animcopybuf.first= animcopybuf.last= NULL; + animcopy_firstframe= 999999999.0f; +} + +/* ------------------- */ + +/* This function adds data to the keyframes copy/paste buffer, freeing existing data first */ +short copy_animedit_keys (bAnimContext *ac, ListBase *anim_data) +{ + bAnimListElem *ale; + + /* clear buffer first */ + free_anim_copybuf(); + + /* assume that each of these is an F-Curve */ + for (ale= anim_data->first; ale; ale= ale->next) { + FCurve *fcu= (FCurve *)ale->key_data; + tAnimCopybufItem *aci; + BezTriple *bezt, *newbuf; + int i; + + /* firstly, check if F-Curve has any selected keyframes + * - skip if no selected keyframes found (so no need to create unnecessary copy-buffer data) + * - this check should also eliminate any problems associated with using sample-data + */ + if (ANIM_fcurve_keys_bezier_loop(NULL, fcu, NULL, ANIM_editkeyframes_ok(BEZT_OK_SELECTED), NULL) == 0) + continue; + + /* init copybuf item info */ + aci= MEM_callocN(sizeof(tAnimCopybufItem), "AnimCopybufItem"); + aci->id= ale->id; + aci->grp= fcu->grp; + aci->rna_path= MEM_dupallocN(fcu->rna_path); + aci->array_index= fcu->array_index; + BLI_addtail(&animcopybuf, aci); + + /* add selected keyframes to buffer */ + // TODO: currently, we resize array everytime we add a new vert - this works ok as long as it is assumed only a few keys are copied + for (i=0, bezt=fcu->bezt; i < fcu->totvert; i++, bezt++) { + if (BEZSELECTED(bezt)) { + /* add to buffer */ + newbuf= MEM_callocN(sizeof(BezTriple)*(aci->totvert+1), "copybuf beztriple"); + + /* assume that since we are just resizing the array, just copy all existing data across */ + if (aci->bezt) + memcpy(newbuf, aci->bezt, sizeof(BezTriple)*(aci->totvert)); + + /* copy current beztriple across too */ + *(newbuf + aci->totvert)= *bezt; + + /* free old array and set the new */ + if (aci->bezt) MEM_freeN(aci->bezt); + aci->bezt= newbuf; + aci->totvert++; + + /* check if this is the earliest frame encountered so far */ + if (bezt->vec[1][0] < animcopy_firstframe) + animcopy_firstframe= bezt->vec[1][0]; + } + } + + } + + /* check if anything ended up in the buffer */ + if (ELEM(NULL, animcopybuf.first, animcopybuf.last)) + return -1; + + /* everything went fine */ + return 0; +} + +/* This function pastes data from the keyframes copy/paste buffer */ +short paste_animedit_keys (bAnimContext *ac, ListBase *anim_data) +{ + bAnimListElem *ale; + const Scene *scene= (ac->scene); + const float offset = (float)(CFRA - animcopy_firstframe); + short no_name= 0; + + /* check if buffer is empty */ + if (ELEM(NULL, animcopybuf.first, animcopybuf.last)) { + //error("No data in buffer to paste"); + return -1; + } + /* check if single channel in buffer (disregard names if so) */ + if (animcopybuf.first == animcopybuf.last) + no_name= 1; + + /* from selected channels */ + for (ale= anim_data->first; ale; ale= ale->next) { + FCurve *fcu = (FCurve *)ale->data; /* destination F-Curve */ + tAnimCopybufItem *aci= NULL; + BezTriple *bezt; + int i; + + /* find buffer item to paste from + * - if names don't matter (i.e. only 1 channel in buffer), don't check id/group + * - if names do matter, only check if id-type is ok for now (group check is not that important) + * - most importantly, rna-paths should match (array indices are unimportant for now) + */ + // TODO: the matching algorithm here is pathetic! + for (aci= animcopybuf.first; aci; aci= aci->next) { + /* check that paths exist */ + if (aci->rna_path && fcu->rna_path) { + if (strcmp(aci->rna_path, fcu->rna_path) == 0) { + /* should be a match unless there's more than one of these */ + if ((no_name) || (aci->array_index == fcu->array_index)) + break; + } + } + } + + + /* copy the relevant data from the matching buffer curve */ + if (aci) { + /* just start pasting, with the the first keyframe on the current frame, and so on */ + for (i=0, bezt=aci->bezt; i < aci->totvert; i++, bezt++) { + /* temporarily apply offset to src beztriple while copying */ + bezt->vec[0][0] += offset; + bezt->vec[1][0] += offset; + bezt->vec[2][0] += offset; + + /* insert the keyframe */ + insert_bezt_fcurve(fcu, bezt); + + /* un-apply offset from src beztriple after copying */ + bezt->vec[0][0] -= offset; + bezt->vec[1][0] -= offset; + bezt->vec[2][0] -= offset; + } + + /* recalculate F-Curve's handles? */ + calchandles_fcurve(fcu); + } + } + + return 0; +} + +/* **************************************************** */ |