This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/animation/keyframes_edit.c
Joshua Leung 833e69ff7e DopeSheet: GPencil-Mode supports Circle and Lasso Select
To get this working the least effort, I've had to expose the helper functions
used by the lasso and circle select keyframe-test callbacks (which are generic)
and expose them for use by the GP keyframe editing code too. Hopefully in time
we clean this all up and just write the code once to operate on "keyframes"
2016-06-24 03:18:36 +12:00

1465 lines
40 KiB
C

/*
* ***** 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2008 Blender Foundation
*
* Contributor(s): Joshua Leung
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/editors/animation/keyframes_edit.c
* \ingroup edanimation
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_utildefines.h"
#include "BLI_lasso.h"
#include "BLI_math.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 ------------------------------------ */
/* This function is used to loop over BezTriples in the given F-Curve, applying a given
* operation on them, and optionally applies an F-Curve validation function afterwards.
*/
// TODO: make this function work on samples too...
short ANIM_fcurve_keyframes_loop(KeyframeEditData *ked, FCurve *fcu, KeyframeEditFunc key_ok, KeyframeEditFunc key_cb, FcuEditFunc fcu_cb)
{
BezTriple *bezt;
short ok = 0;
unsigned int i;
/* sanity check */
if (ELEM(NULL, fcu, fcu->bezt))
return 0;
/* set the F-Curve into the editdata 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 fullfills 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; // curves only
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 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; // curves only
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 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 localised 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;
}
/* --- */
/* This function is used to apply operation to all keyframes, regardless of the type */
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;
}
/* This function is used to apply operation to all keyframes, regardless of the type without needed an AnimListElem wrapper */
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;
}
/* ************************************************************************** */
/* Keyframe Integrity Tools */
/* Rearrange keyframes if some are out of order */
// used to be recalc_*_ipos() where * was object or action
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 check them */
for (ale = anim_data.first; ale; ale = ale->next) {
FCurve *fcu = ale->key_data;
/* make sure keyframes in F-Curve are all in order, and handles are in valid positions */
sort_time_fcurve(fcu);
calchandles_fcurve(fcu);
}
/* free temp 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)) { \
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;
else
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;
}
else
return 0;
}
/**
* Called from #ok_bezier_region_lasso and #ok_bezier_channel_lasso
*/
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->mcords, data_lasso->mcords_tot, 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;
}
else
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;
}
/**
* Called from #ok_bezier_region_circle and #ok_bezier_channel_circle
*/
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;
}
else
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 */
/* helper callback for <animeditor>_cfrasnap_exec() -> used to help get the average time of all selected beztriples */
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;
}
/* helper callback for columnselect_<animeditor>_keys() -> populate list CfraElems with frame numbers from selected beztriples */
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;
}
/* used to remap times from one range to another
* requires: ked->data = KeyframeEditCD_Remap
*/
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 neares 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)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 type)
{
/* eEditKeyframes_Snap */
switch (type) {
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)
{
float diff;
int i;
for (i = 0; i < 3; i++) {
diff = (center - bezt->vec[i][0]);
bezt->vec[i][0] = (center + diff);
}
swap_v3_v3(bezt->vec[0], bezt->vec[2]);
SWAP(char, bezt->h1, bezt->h2);
SWAP(char, bezt->f1, bezt->f3);
}
static void mirror_bezier_yaxis_ex(BezTriple *bezt, const float center)
{
float diff;
int i;
for (i = 0; i < 3; i++) {
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, CFRA);
}
return 0;
}
static short mirror_bezier_yaxis(KeyframeEditData *UNUSED(ked), BezTriple *bezt)
{
if (bezt->f2 & SELECT) {
/* Yes, names are inverted, we are mirroring accross 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 accross 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 strored 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;
}
/* Note: for markers and 'value', the values to use must be supplied as the first float value */
// calchandles_fcurve
KeyframeEditFunc ANIM_editkeyframes_mirror(short type)
{
switch (type) {
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 ((bezt->f1 & SELECT) || (bezt->f3 & SELECT)) {
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 ((bezt->f1 & SELECT) || (bezt->f3 & SELECT)) {
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 (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 (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 (bezt->f1 & SELECT) bezt->h1 = HD_FREE;
if (bezt->f3 & SELECT) bezt->h2 = HD_FREE;
return 0;
}
/* Set all selected Bezier Handles to a single type */
// calchandles_fcurve
KeyframeEditFunc ANIM_editkeyframes_handles(short code)
{
switch (code) {
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;
}
/* Set the interpolation type of the selected BezTriples in each F-Curve to the specified one */
// ANIM_editkeyframes_ipocurve_ipotype() !
KeyframeEditFunc ANIM_editkeyframes_ipo(short code)
{
switch (code) {
/* 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;
}
/* Set the interpolation type of the selected BezTriples in each F-Curve to the specified one */
KeyframeEditFunc ANIM_editkeyframes_keytype(short code)
{
switch (code) {
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_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;
}
/* Set the easing type of the selected BezTriples in each F-Curve to the specified one */
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)
{
/* if we've got info on what to select, use it, otherwise select all */
if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES)) {
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)
{
/* if we've got info on what to deselect, use it, otherwise deselect all */
if ((ked) && (ked->iterflags & KEYFRAME_ITER_INCL_HANDLES)) {
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;
}
/* Get callback for building selection map */
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;
}
}
/* ----------- */
/* flush selection map values to the given beztriple */
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;
}