1
1

Compare commits

...

4 Commits

Author SHA1 Message Date
efb558fc8b Preserve F-Curve shape with manual handles when inserting keyframes.
During animation it is preferrable that inserting a keyframe without
changing object position doesn't change the curve shape. For curves
that use Auto keyframes this has been largely addressed by the new
algorithm from D2884, but that doesn't apply to non-auto handles.

This patch applies the de-Casteljau algorithm to split the bezier
curve at the X coordinate where the new key is inserted, and uses
the result to update both the newly inserted and existing surrounding
handles.

Key deletion code is also modified so that it attempts to revert
the effect of an assumed prior subdivision done by the above method.
Of course, when the key being deleted isn't actually redundant, it
is impossible to preserve the shape of the curve.

The idea for this patch come from D2642, but the implementation has
been almost completely rewritten to use existing math code better.

Also, contrary to that patch, this version considers that switching
a key from Auto to manual incurs a significant usability cost,
and only ever does it if the curve shape is already defined by
manual handles. When dealing with a curve area that is purely
Auto, the new code won't even run.

Reviewers: aligorith

Differential Revision: https://developer.blender.org/D3172
2018-04-22 19:52:05 +03:00
dfc187d411 Expose info whether the FCurve is considered trivially cyclic to python.
Since this is determined by a certain subset of possible settings of
a modifier, independently computing this in python may be error prone.
2018-04-22 16:35:56 +03:00
de27f054af Experimental: display keyframe interpolation mode in dopesheet.
Allow distinguishing 7 basic interpolation classes. Currently the
dopesheet allows changing interpolation and handle types, but does
not show the current type in any way.
2018-04-22 16:35:56 +03:00
bd66965f31 Add an option to do keyframe insertion in a cycle-aware fashion.
When enabled, inserting keyframes into F-Curves with simple cyclic
extrapolation (the same conditions as required for cycle-aware auto
handle smoothing to activate) will take the cycle into account:

- Keyframes that are being inserted outside of the cycle bounds
  are remapped to be inside the cycle. Thus it is not necessary
  to be within the main iteration of the cycle when tweaking.

  This becomes especially useful in the final animation tweaking
  phase when the channel keys may be staggered for overlap, so
  the actual master period is different for different channels.

- Modifying one of the end points of a cycle also changes the
  other end point when appropriate, to preserve smooth transition.

For now this option is ignored in action and graph editor, since
there the user more directly operates on the keyframe sequence,
in contrast with (auto) keyframing through 3d view.

Reviewers: aligorith

Differential Revision: https://developer.blender.org/D3140
2018-04-22 16:35:56 +03:00
17 changed files with 484 additions and 59 deletions

View File

@@ -97,6 +97,8 @@ class TIME_HT_header(Header):
row.operator("anim.keyframe_insert", text="", icon='KEY_HLT') row.operator("anim.keyframe_insert", text="", icon='KEY_HLT')
row.operator("anim.keyframe_delete", text="", icon='KEY_DEHLT') row.operator("anim.keyframe_delete", text="", icon='KEY_DEHLT')
layout.prop(toolsettings, "use_keyframe_cycle_aware", text="", toggle=True)
class TIME_MT_editor_menus(Menu): class TIME_MT_editor_menus(Menu):
bl_idname = "TIME_MT_editor_menus" bl_idname = "TIME_MT_editor_menus"

View File

@@ -269,6 +269,14 @@ bool BKE_fcurve_is_protected(struct FCurve *fcu);
/* The curve is an infinite cycle via Cycles modifier */ /* The curve is an infinite cycle via Cycles modifier */
bool BKE_fcurve_is_cyclic(struct FCurve *fcu); bool BKE_fcurve_is_cyclic(struct FCurve *fcu);
typedef enum eFCU_Cycle_Type {
FCU_CYCLE_NONE = 0,
FCU_CYCLE_PERFECT,
FCU_CYCLE_OFFSET
} eFCU_Cycle_Type;
eFCU_Cycle_Type BKE_fcurve_get_cycle_type(struct FCurve *fcu);
/* -------- Curve Sanity -------- */ /* -------- Curve Sanity -------- */
void calchandles_fcurve(struct FCurve *fcu); void calchandles_fcurve(struct FCurve *fcu);
@@ -278,6 +286,14 @@ short test_time_fcurve(struct FCurve *fcu);
void correct_bezpart(float v1[2], float v2[2], float v3[2], float v4[2]); void correct_bezpart(float v1[2], float v2[2], float v3[2], float v4[2]);
/* Adjust bezier handles as bezt is being inserted between prev and next.
* - pdelta: outputs Y difference between bezt and old curve value at its X position.
*/
bool BKE_bezt_subdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, float *pdelta);
/* Try to revert prev & next handle adjustment performed by subdivide_handles. */
bool BKE_bezt_unsubdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next);
/* -------- Evaluation -------- */ /* -------- Evaluation -------- */
/* evaluate fcurve */ /* evaluate fcurve */

View File

@@ -883,24 +883,37 @@ void fcurve_store_samples(FCurve *fcu, void *data, int start, int end, FcuSample
*/ */
/* Checks if the F-Curve has a Cycles modifier with simple settings that warrant transition smoothing */ /* Checks if the F-Curve has a Cycles modifier with simple settings that warrant transition smoothing */
bool BKE_fcurve_is_cyclic(FCurve *fcu) eFCU_Cycle_Type BKE_fcurve_get_cycle_type(FCurve *fcu)
{ {
FModifier *fcm = fcu->modifiers.first; FModifier *fcm = fcu->modifiers.first;
if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES) if (!fcm || fcm->type != FMODIFIER_TYPE_CYCLES)
return false; return FCU_CYCLE_NONE;
if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED)) if (fcm->flag & (FMODIFIER_FLAG_DISABLED | FMODIFIER_FLAG_MUTED))
return false; return FCU_CYCLE_NONE;
if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE)) if (fcm->flag & (FMODIFIER_FLAG_RANGERESTRICT | FMODIFIER_FLAG_USEINFLUENCE))
return false; return FCU_CYCLE_NONE;
FMod_Cycles *data = (FMod_Cycles *)fcm->data; FMod_Cycles *data = (FMod_Cycles *)fcm->data;
return data && data->after_cycles == 0 && data->before_cycles == 0 && if (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); if (data->before_mode == FCM_EXTRAPOLATE_CYCLIC && data->after_mode == FCM_EXTRAPOLATE_CYCLIC)
return FCU_CYCLE_PERFECT;
if (ELEM(data->before_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET) &&
ELEM(data->after_mode, FCM_EXTRAPOLATE_CYCLIC, FCM_EXTRAPOLATE_CYCLIC_OFFSET))
return FCU_CYCLE_OFFSET;
}
return FCU_CYCLE_NONE;
}
bool BKE_fcurve_is_cyclic(FCurve *fcu)
{
return BKE_fcurve_get_cycle_type(fcu) != FCU_CYCLE_NONE;
} }
/* Shifts 'in' by the difference in coordinates between 'to' and 'from', using 'out' as the output buffer. /* Shifts 'in' by the difference in coordinates between 'to' and 'from', using 'out' as the output buffer.
@@ -2076,17 +2089,12 @@ void correct_bezpart(float v1[2], float v2[2], float v3[2], float v4[2])
} }
} }
/* find root ('zero') */ /* find roots of cubic equation */
static int findzero(float x, float q0, float q1, float q2, float q3, float *o) static int solve_cubic(float c0, float c1, float c2, float c3, float *o)
{ {
double c0, c1, c2, c3, a, b, c, p, q, d, t, phi; double a, b, c, p, q, d, t, phi;
int nr = 0; int nr = 0;
c0 = q0 - x;
c1 = 3.0f * (q1 - q0);
c2 = 3.0f * (q0 - 2.0f * q1 + q2);
c3 = q3 - q0 + 3.0f * (q1 - q2);
if (c3 != 0.0) { if (c3 != 0.0) {
a = c2 / c3; a = c2 / c3;
b = c1 / c3; b = c1 / c3;
@@ -2171,6 +2179,19 @@ static int findzero(float x, float q0, float q1, float q2, float q3, float *o)
} }
} }
/* find root ('zero') */
static int findzero(float x, float q0, float q1, float q2, float q3, float *o)
{
double c0, c1, c2, c3;
c0 = q0 - x;
c1 = 3.0f * (q1 - q0);
c2 = 3.0f * (q0 - 2.0f * q1 + q2);
c3 = q3 - q0 + 3.0f * (q1 - q2);
return solve_cubic(c0, c1, c2, c3, o);
}
static void berekeny(float f1, float f2, float f3, float f4, float *o, int b) static void berekeny(float f1, float f2, float f3, float f4, float *o, int b)
{ {
float t, c0, c1, c2, c3; float t, c0, c1, c2, c3;
@@ -2205,6 +2226,104 @@ static void berekenx(float *f, float *o, int b)
} }
#endif #endif
/* recompute handles to neatly subdivide the prev-next range at bezt */
bool BKE_bezt_subdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next, float *pdelta)
{
float *P0 = prev->vec[1], *P1 = prev->vec[2], *P2 = next->vec[0], *P3 = next->vec[1];
float *ML = bezt->vec[0], *MM = bezt->vec[1], *MR = bezt->vec[2];
if (MM[0] <= P0[0] || MM[0] >= P3[0])
return false;
/* find bezier parameter */
correct_bezpart(P0, P1, P2, P3);
float keys[4];
if (!findzero(MM[0], P0[0], P1[0], P2[0], P3[0], keys))
return false;
float t = keys[0];
if (t <= 0.0f || t >= 1.0f)
return false;
/* de-Casteljau split */
float M1[3][2], M2[2][2], M3[2], D[2];
interp_v2_v2v2(M1[0], P0, P1, t);
interp_v2_v2v2(M1[1], P1, P2, t);
interp_v2_v2v2(M1[2], P2, P3, t);
interp_v2_v2v2(M2[0], M1[0], M1[1], t);
interp_v2_v2v2(M2[1], M1[1], M1[2], t);
interp_v2_v2v2(M3, M2[0], M2[1], t);
/* overwrite the handles */
copy_v2_v2(P1, M1[0]);
copy_v2_v2(P2, M1[2]);
sub_v2_v2v2(D, MM, M3);
add_v2_v2v2(ML, M2[0], D);
add_v2_v2v2(MR, M2[1], D);
*pdelta = D[1];
return true;
}
/* try to restore handles before deletion of bezt */
bool BKE_bezt_unsubdivide_handles(struct BezTriple *bezt, struct BezTriple *prev, struct BezTriple *next)
{
float *P0 = prev->vec[1], *P1 = prev->vec[2], *P2 = next->vec[0], *P3 = next->vec[1];
float *ML = bezt->vec[0], *MM = bezt->vec[1], *MR = bezt->vec[2];
if (MM[0] <= P0[0] || MM[0] >= P3[0])
return false;
/* Solve equation to undo P1 & P2 rescaling, assuming basically that
* prev and next were adjusted by BKE_bezt_subdivide_handles before.
* The equation tries to find t that was used at that time.
*/
correct_bezpart(P0, P1, ML, MM);
correct_bezpart(MM, MR, P2, P3);
float c0, c1, c2, c3, keys[4];
c0 = 3.0f * P1[0] - 2.0f * P0[0] - MM[0];
c1 = 6.0f * (P0[0] - P1[0]);
c2 = 3.0f * (P1[0] + P2[0]) - 6.0f * P0[0];
c3 = 2.0f * (P0[0] - P3[0]);
float t, tx = (MM[0] - P0[0]) / (P3[0] - P0[0]);
int n = solve_cubic(c0, c1, c2, c3, keys);
if (n < 1) {
/* if the equation failed, just use the X coordinate ratio */
t = tx;
}
else {
/* find the best solution given by the equation */
t = keys[0];
for (int i = 1; i < n; i++)
if (fabsf(keys[i] - tx) < fabsf(t - tx))
t = keys[i];
}
if (t <= 0.0f || t >= 1.0f)
return false;
/* rescale handles */
float tmp[2];
sub_v2_v2v2(tmp, P1, P0);
madd_v2_v2v2fl(P1, P0, tmp, 1.0f / t);
sub_v2_v2v2(tmp, P2, P3);
madd_v2_v2v2fl(P2, P3, tmp, 1.0f / (1.0f - t));
return true;
}
/* -------------------------- */ /* -------------------------- */

View File

@@ -99,6 +99,29 @@ static short compare_ak_bezt(void *node, void *data)
return 0; return 0;
} }
static eKeyframeInterpolationDrawOpts bezt_interpolation_type(BezTriple *bezt)
{
switch (bezt->ipo)
{
case BEZT_IPO_CONST:
return KEYFRAME_IPOTYPE_CONST;
case BEZT_IPO_LIN:
return KEYFRAME_IPOTYPE_LINEAR;
case BEZT_IPO_BEZ:
if (bezt->h1 == HD_AUTO_ANIM && bezt->h2 == HD_AUTO_ANIM)
return KEYFRAME_IPOTYPE_BEZ_AUTO_CLAMP;
else if (ELEM(bezt->h1, HD_AUTO_ANIM, HD_AUTO) && ELEM(bezt->h2, HD_AUTO_ANIM, HD_AUTO))
return KEYFRAME_IPOTYPE_BEZ_AUTO;
else if (bezt->h1 == HD_VECT && bezt->h2 == HD_VECT)
return KEYFRAME_IPOTYPE_BEZ_VECTOR;
else
return KEYFRAME_IPOTYPE_BEZ_MANUAL;
default:
return KEYFRAME_IPOTYPE_EASING;
}
}
/* New node callback used for building ActKeyColumns from BezTriples */ /* New node callback used for building ActKeyColumns from BezTriples */
static DLRBT_Node *nalloc_ak_bezt(void *data) static DLRBT_Node *nalloc_ak_bezt(void *data)
{ {
@@ -109,6 +132,7 @@ static DLRBT_Node *nalloc_ak_bezt(void *data)
ak->cfra = bezt->vec[1][0]; ak->cfra = bezt->vec[1][0];
ak->sel = BEZT_ISSEL_ANY(bezt) ? SELECT : 0; ak->sel = BEZT_ISSEL_ANY(bezt) ? SELECT : 0;
ak->key_type = BEZKEYTYPE(bezt); ak->key_type = BEZKEYTYPE(bezt);
ak->ipo_type = bezt_interpolation_type(bezt);
/* set 'modified', since this is used to identify long keyframes */ /* set 'modified', since this is used to identify long keyframes */
ak->modified = 1; ak->modified = 1;
@@ -129,6 +153,8 @@ static void nupdate_ak_bezt(void *node, void *data)
/* for keyframe type, 'proper' keyframes have priority over breakdowns (and other types for now) */ /* for keyframe type, 'proper' keyframes have priority over breakdowns (and other types for now) */
if (BEZKEYTYPE(bezt) == BEZT_KEYTYPE_KEYFRAME) if (BEZKEYTYPE(bezt) == BEZT_KEYTYPE_KEYFRAME)
ak->key_type = BEZT_KEYTYPE_KEYFRAME; ak->key_type = BEZT_KEYTYPE_KEYFRAME;
ak->ipo_type = MAX2(ak->ipo_type, bezt_interpolation_type(bezt));
} }
/* ......... */ /* ......... */
@@ -471,8 +497,99 @@ static const float _unit_diamond_shape[4][2] = {
{-1.0f, 0.0f} /* mid-left */ {-1.0f, 0.0f} /* mid-left */
}; };
static const float _unit_mini_diamond_shape[4][2] = {
{0.0f, 0.1f}, /* top vert */
{0.1f, 0.0f}, /* mid-right */
{0.0f, -0.1f}, /* bottom vert */
{-0.1f, 0.0f} /* mid-left */
};
static const float _unit_ipo_top_left[2] = {-0.5f, 0.5f};
static const float _unit_ipo_top_right[2] = {0.5f, 0.5f};
static const float _unit_ipo_bottom_left[2] = {-0.5f, -0.5f};
static const float _unit_ipo_bottom_right[2] = {0.5f, -0.5f};
static const float _unit_ipo_middle[2] = {0.0f, 0.0f};
static void draw_ipo_type(short ipo_type)
{
static GLuint ipo_disps[10] = { 0 };
if (ipo_type == KEYFRAME_IPOTYPE_NONE || ipo_type == KEYFRAME_IPOTYPE_BEZ_AUTO_CLAMP)
return;
if (ipo_disps[ipo_type] == 0) {
ipo_disps[ipo_type] = glGenLists(1);
glNewList(ipo_disps[ipo_type], GL_COMPILE);
switch (ipo_type) {
case KEYFRAME_IPOTYPE_CONST: // <|>
glBegin(GL_LINES);
glVertex2fv(_unit_diamond_shape[0]);
glVertex2fv(_unit_diamond_shape[2]);
glEnd();
break;
case KEYFRAME_IPOTYPE_LINEAR: // <->
glBegin(GL_LINES);
glVertex2fv(_unit_diamond_shape[1]);
glVertex2fv(_unit_diamond_shape[3]);
glEnd();
break;
case KEYFRAME_IPOTYPE_EASING: // <+>
glBegin(GL_LINES);
glVertex2fv(_unit_diamond_shape[0]);
glVertex2fv(_unit_diamond_shape[2]);
glVertex2fv(_unit_diamond_shape[1]);
glVertex2fv(_unit_diamond_shape[3]);
glEnd();
break;
case KEYFRAME_IPOTYPE_BEZ_AUTO: // <.>
glBegin(GL_LINE_LOOP);
glVertex2fv(_unit_mini_diamond_shape[0]);
glVertex2fv(_unit_mini_diamond_shape[1]);
glVertex2fv(_unit_mini_diamond_shape[2]);
glVertex2fv(_unit_mini_diamond_shape[3]);
glEnd();
break;
/*case KEYFRAME_IPOTYPE_BEZ_AUTO: // </>
glBegin(GL_LINES);
glVertex2fv(_unit_ipo_bottom_left);
glVertex2fv(_unit_ipo_top_right);
glEnd();
break;*/
case KEYFRAME_IPOTYPE_BEZ_VECTOR: // <v>
glBegin(GL_LINE_STRIP);
glVertex2fv(_unit_ipo_top_left);
glVertex2fv(_unit_ipo_middle);
glVertex2fv(_unit_ipo_top_right);
glEnd();
break;
case KEYFRAME_IPOTYPE_BEZ_MANUAL: // <X>
glBegin(GL_LINES);
glVertex2fv(_unit_ipo_top_left);
glVertex2fv(_unit_ipo_bottom_right);
glVertex2fv(_unit_ipo_bottom_left);
glVertex2fv(_unit_ipo_top_right);
glEnd();
break;
default:
break;
}
glEndList();
}
glCallList(ipo_disps[ipo_type]);
}
/* draw a simple diamond shape with OpenGL */ /* draw a simple diamond shape with OpenGL */
void draw_keyframe_shape(float x, float y, float xscale, float hsize, short sel, short key_type, short mode, float alpha) void draw_keyframe_shape(float x, float y, float xscale, float hsize, short sel, short key_type, short mode, float alpha, short ipo_type)
{ {
static GLuint displist1 = 0; static GLuint displist1 = 0;
static GLuint displist2 = 0; static GLuint displist2 = 0;
@@ -599,6 +716,8 @@ void draw_keyframe_shape(float x, float y, float xscale, float hsize, short sel,
border_col[3] *= alpha; border_col[3] *= alpha;
glColor4fv(border_col); glColor4fv(border_col);
draw_ipo_type(ipo_type);
glCallList(displist1); glCallList(displist1);
} }
@@ -682,7 +801,7 @@ static void draw_keylist(View2D *v2d, DLRBT_Tree *keys, DLRBT_Tree *blocks, floa
/* draw using OpenGL - uglier but faster */ /* draw using OpenGL - uglier but faster */
/* NOTE1: a previous version of this didn't work nice for some intel cards /* NOTE1: a previous version of this didn't work nice for some intel cards
* NOTE2: if we wanted to go back to icons, these are icon = (ak->sel & SELECT) ? ICON_SPACE2 : ICON_SPACE3; */ * NOTE2: if we wanted to go back to icons, these are icon = (ak->sel & SELECT) ? ICON_SPACE2 : ICON_SPACE3; */
draw_keyframe_shape(ak->cfra, ypos, xscale, iconsize, (ak->sel & SELECT), ak->key_type, KEYFRAME_SHAPE_BOTH, alpha); draw_keyframe_shape(ak->cfra, ypos, xscale, iconsize, (ak->sel & SELECT), ak->key_type, KEYFRAME_SHAPE_BOTH, alpha, ak->ipo_type);
} }
} }

View File

@@ -71,6 +71,18 @@
/* **************************************************** */ /* **************************************************** */
static void unsubdivide_nonauto_handles(BezTriple *bezt, BezTriple *prev, BezTriple *next)
{
if (prev->ipo != BEZT_IPO_BEZ || bezt->ipo != BEZT_IPO_BEZ)
return;
if (ELEM(prev->h2, HD_AUTO, HD_AUTO_ANIM, HD_VECT) &&
ELEM(next->h1, HD_AUTO, HD_AUTO_ANIM, HD_VECT))
return;
BKE_bezt_unsubdivide_handles(bezt, prev, next);
}
/* Only delete the nominated keyframe from provided F-Curve. /* Only delete the nominated keyframe from provided F-Curve.
* Not recommended to be used many times successively. For that * Not recommended to be used many times successively. For that
* there is delete_fcurve_keys(). * there is delete_fcurve_keys().
@@ -90,6 +102,10 @@ void delete_fcurve_key(FCurve *fcu, int index, bool do_recalc)
else if (index < 0) else if (index < 0)
index += fcu->totvert; index += fcu->totvert;
/* resize surrounding non-auto bezier handles */
if (do_recalc && index > 0 && index < fcu->totvert - 1)
unsubdivide_nonauto_handles(&fcu->bezt[index], &fcu->bezt[index - 1], &fcu->bezt[index + 1]);
/* Delete this keyframe */ /* Delete this keyframe */
memmove(&fcu->bezt[index], &fcu->bezt[index + 1], sizeof(BezTriple) * (fcu->totvert - index - 1)); memmove(&fcu->bezt[index], &fcu->bezt[index + 1], sizeof(BezTriple) * (fcu->totvert - index - 1));
fcu->totvert--; fcu->totvert--;
@@ -105,8 +121,19 @@ void delete_fcurve_key(FCurve *fcu, int index, bool do_recalc)
calchandles_fcurve(fcu); calchandles_fcurve(fcu);
} }
/* Checks if all keys are selected in an fcurve */
static bool check_if_all_keys_sel(struct FCurve *fcu)
{
for (int i = 0; i < fcu->totvert; i++)
{
if (!(fcu->bezt[i].f2 & SELECT))
return false;
}
return true;
}
/* Delete selected keyframes in given F-Curve */ /* Delete selected keyframes in given F-Curve */
bool delete_fcurve_keys(FCurve *fcu) bool delete_fcurve_keys(FCurve *fcu, bool resize_handles)
{ {
int i; int i;
bool changed = false; bool changed = false;
@@ -114,9 +141,19 @@ bool delete_fcurve_keys(FCurve *fcu)
if (fcu->bezt == NULL) /* ignore baked curves */ if (fcu->bezt == NULL) /* ignore baked curves */
return false; return false;
/* Clear if everything is selected */
if (check_if_all_keys_sel(fcu)) {
clear_fcurve_keys(fcu);
return true;
}
/* Delete selected BezTriples */ /* Delete selected BezTriples */
for (i = 0; i < fcu->totvert; i++) { for (i = 0; i < fcu->totvert; i++) {
if (fcu->bezt[i].f2 & SELECT) { if (fcu->bezt[i].f2 & SELECT) {
/* resize surrounding non-auto bezier handles */
if (resize_handles && i > 0 && i < fcu->totvert - 1)
unsubdivide_nonauto_handles(&fcu->bezt[i], &fcu->bezt[i-1], &fcu->bezt[i+1]);
memmove(&fcu->bezt[i], &fcu->bezt[i + 1], sizeof(BezTriple) * (fcu->totvert - i - 1)); memmove(&fcu->bezt[i], &fcu->bezt[i + 1], sizeof(BezTriple) * (fcu->totvert - i - 1));
fcu->totvert--; fcu->totvert--;
i--; i--;
@@ -860,7 +897,7 @@ static void paste_animedit_keys_fcurve(FCurve *fcu, tAnimCopybufItem *aci, float
} }
/* remove frames in the range */ /* remove frames in the range */
delete_fcurve_keys(fcu); delete_fcurve_keys(fcu, false);
} }
break; break;
} }

View File

@@ -112,6 +112,10 @@ short ANIM_get_keyframing_flags(Scene *scene, short incl_mode)
/* keyframing mode - only replace existing keyframes */ /* keyframing mode - only replace existing keyframes */
if (IS_AUTOKEY_MODE(scene, EDITKEYS)) if (IS_AUTOKEY_MODE(scene, EDITKEYS))
flag |= INSERTKEY_REPLACE; flag |= INSERTKEY_REPLACE;
/* cycle-aware keyframe insertion - preserve cycle period and flow */
if (IS_AUTOKEY_FLAG(scene, CYCLEAWARE))
flag |= INSERTKEY_CYCLE_AWARE;
} }
return flag; return flag;
@@ -293,8 +297,57 @@ void update_autoflags_fcurve(FCurve *fcu, bContext *C, ReportList *reports, Poin
/* ************************************************** */ /* ************************************************** */
/* KEYFRAME INSERTION */ /* KEYFRAME INSERTION */
static eFCU_Cycle_Type remap_cyclic_keyframe_location(FCurve *fcu, float *px, float *py)
{
if (fcu->totvert < 2 || !fcu->bezt)
return FCU_CYCLE_NONE;
eFCU_Cycle_Type type = BKE_fcurve_get_cycle_type(fcu);
if (!type)
return FCU_CYCLE_NONE;
BezTriple *first = &fcu->bezt[0], *last = &fcu->bezt[fcu->totvert-1];
float start = first->vec[1][0], end = last->vec[1][0];
if (start >= end)
return FCU_CYCLE_NONE;
if (*px < start || *px > end) {
float period = end - start;
float step = floorf((*px - start) / period);
*px -= step * period;
if (type == FCU_CYCLE_OFFSET) {
/* nasty check to handle the case when the modes are different better */
FMod_Cycles *data = (FMod_Cycles *)((FModifier*)fcu->modifiers.first)->data;
if ((step >= 0 ? data->after_mode : data->before_mode) == FCM_EXTRAPOLATE_CYCLIC_OFFSET) {
*py -= step * (last->vec[1][1] - first->vec[1][1]);
}
}
}
return type;
}
/* -------------- BezTriple Insertion -------------------- */ /* -------------- BezTriple Insertion -------------------- */
static void replace_bezt_keyframe_ypos(BezTriple *dst, const BezTriple *bezt)
{
/* just change the values when replacing, so as to not overwrite handles */
float dy = bezt->vec[1][1] - dst->vec[1][1];
/* just apply delta value change to the handle values */
dst->vec[0][1] += dy;
dst->vec[1][1] += dy;
dst->vec[2][1] += dy;
dst->f1 = bezt->f1;
dst->f2 = bezt->f2;
dst->f3 = bezt->f3;
/* TODO: perform some other operations? */
}
/* This function adds a given BezTriple to an F-Curve. It will allocate /* This function adds a given BezTriple to an F-Curve. It will allocate
* memory for the array if needed, and will insert the BezTriple into a * memory for the array if needed, and will insert the BezTriple into a
* suitable place in chronological order. * suitable place in chronological order.
@@ -319,20 +372,13 @@ int insert_bezt_fcurve(FCurve *fcu, const BezTriple *bezt, eInsertKeyFlags flag)
fcu->bezt[i] = *bezt; fcu->bezt[i] = *bezt;
} }
else { else {
/* just change the values when replacing, so as to not overwrite handles */ replace_bezt_keyframe_ypos(&fcu->bezt[i], bezt);
BezTriple *dst = (fcu->bezt + i); }
float dy = bezt->vec[1][1] - dst->vec[1][1];
/* if replacing an end point of a cyclic curve without offset, modify the other end too. */
/* just apply delta value change to the handle values */ if ((flag & INSERTKEY_CYCLE_AWARE) && (i == 0 || i == fcu->totvert-1) &&
dst->vec[0][1] += dy; (BKE_fcurve_get_cycle_type(fcu) == FCU_CYCLE_PERFECT)) {
dst->vec[1][1] += dy; replace_bezt_keyframe_ypos(&fcu->bezt[(fcu->totvert-1) - i], bezt);
dst->vec[2][1] += dy;
dst->f1 = bezt->f1;
dst->f2 = bezt->f2;
dst->f3 = bezt->f3;
/* TODO: perform some other operations? */
} }
} }
} }
@@ -384,6 +430,47 @@ int insert_bezt_fcurve(FCurve *fcu, const BezTriple *bezt, eInsertKeyFlags flag)
return i; return i;
} }
/* This checks whether it is necessary to apply bezier subdivision due to involvement of non-auto handles.
* - bezt: key being inserted
* - prev, next: old keys surrounding the location
*/
static void subdivide_nonauto_handles(FCurve *fcu, BezTriple *bezt, BezTriple *prev, BezTriple *next)
{
if (prev->ipo != BEZT_IPO_BEZ || bezt->ipo != BEZT_IPO_BEZ)
return;
/* don't change vector handles, or completely auto regions */
bool bezt_auto = BEZT_IS_AUTOH(bezt) || (bezt->h1 == HD_VECT && bezt->h2 == HD_VECT);
bool prev_auto = BEZT_IS_AUTOH(prev) || (prev->h2 == HD_VECT);
bool next_auto = BEZT_IS_AUTOH(next) || (next->h1 == HD_VECT);
if (bezt_auto && prev_auto && next_auto)
return;
/* subdivide the curve */
float delta;
if (!BKE_bezt_subdivide_handles(bezt, prev, next, &delta))
return;
/* decide when to force auto to manual */
if (BEZT_IS_AUTOH(bezt) && fabsf(delta) < 0.001f) {
bool auto_works_well = false;
if (fcu->auto_smoothing == FCURVE_SMOOTH_CONT_ACCEL) {
float hx = bezt->vec[1][0] - bezt->vec[0][0];
float dx = bezt->vec[1][0] - prev->vec[1][0];
/* this mode always uses 1/3 of key distance for handle x size */
auto_works_well = fabsf(hx - dx/3.0f) < 0.001f;
}
if (!auto_works_well || !(prev_auto || next_auto)) {
bezt->h1 = bezt->h2 = HD_ALIGN;
}
}
}
/** /**
* This function is a wrapper for insert_bezt_fcurve_internal(), and should be used when * This function is a wrapper for insert_bezt_fcurve_internal(), and should be used when
* adding a new keyframe to a curve, when the keyframe doesn't exist anywhere else yet. * adding a new keyframe to a curve, when the keyframe doesn't exist anywhere else yet.
@@ -456,13 +543,6 @@ int insert_vert_fcurve(FCurve *fcu, float x, float y, eBezTriple_KeyframeType ke
*/ */
if (a < 0) return -1; if (a < 0) return -1;
/* don't recalculate handles if fast is set
* - this is a hack to make importers faster
* - we may calculate twice (due to autohandle needing to be calculated twice)
*/
if ((flag & INSERTKEY_FAST) == 0)
calchandles_fcurve(fcu);
/* set handletype and interpolation */ /* set handletype and interpolation */
if ((fcu->totvert > 2) && (flag & INSERTKEY_REPLACE) == 0) { if ((fcu->totvert > 2) && (flag & INSERTKEY_REPLACE) == 0) {
BezTriple *bezt = (fcu->bezt + a); BezTriple *bezt = (fcu->bezt + a);
@@ -476,15 +556,17 @@ int insert_vert_fcurve(FCurve *fcu, float x, float y, eBezTriple_KeyframeType ke
bezt->ipo = (bezt - 1)->ipo; bezt->ipo = (bezt - 1)->ipo;
else if (a < fcu->totvert - 1) else if (a < fcu->totvert - 1)
bezt->ipo = (bezt + 1)->ipo; bezt->ipo = (bezt + 1)->ipo;
if ((a > 0) && (a < fcu->totvert - 1) && (flag & INSERTKEY_OVERWRITE_FULL) == 0)
subdivide_nonauto_handles(fcu, bezt, bezt - 1, bezt + 1);
} }
/* don't recalculate handles if fast is set
* - this is a hack to make importers faster
* - we may calculate twice (due to autohandle needing to be calculated twice)
*/
if ((flag & INSERTKEY_FAST) == 0)
calchandles_fcurve(fcu);
} }
/* don't recalculate handles if fast is set
* - this is a hack to make importers faster
*/
if ((flag & INSERTKEY_FAST) == 0)
calchandles_fcurve(fcu);
/* return the index at which the keyframe was added */ /* return the index at which the keyframe was added */
return a; return a;
@@ -960,6 +1042,14 @@ bool insert_keyframe_direct(ReportList *reports, PointerRNA ptr, PropertyRNA *pr
curval = setting_get_rna_value(&ptr, prop, fcu->array_index); curval = setting_get_rna_value(&ptr, prop, fcu->array_index);
} }
/* adjust coordinates for cycle aware insertion */
if (flag & INSERTKEY_CYCLE_AWARE) {
if (remap_cyclic_keyframe_location(fcu, &cfra, &curval) != FCU_CYCLE_PERFECT) {
/* inhibit action from insert_vert_fcurve unless it's a perfect cycle */
flag &= ~INSERTKEY_CYCLE_AWARE;
}
}
/* only insert keyframes where they are needed */ /* only insert keyframes where they are needed */
if (flag & INSERTKEY_NEEDED) { if (flag & INSERTKEY_NEEDED) {
short insert_mode; short insert_mode;

View File

@@ -923,7 +923,8 @@ short ANIM_validate_keyingset(bContext *C, ListBase *dsources, KeyingSet *ks)
/* Determine which keying flags apply based on the override flags */ /* Determine which keying flags apply based on the override flags */
static short keyingset_apply_keying_flags(const short base_flags, const short overrides, const short own_flags) static short keyingset_apply_keying_flags(const short base_flags, const short overrides, const short own_flags)
{ {
short result = 0; /* Pass through all flags by default (i.e. even not explicitly listed ones). */
short result = base_flags;
/* The logic for whether a keying flag applies is as follows: /* The logic for whether a keying flag applies is as follows:
* - If the flag in question is set in "overrides", that means that the * - If the flag in question is set in "overrides", that means that the
@@ -933,12 +934,10 @@ static short keyingset_apply_keying_flags(const short base_flags, const short ov
*/ */
#define APPLY_KEYINGFLAG_OVERRIDE(kflag) \ #define APPLY_KEYINGFLAG_OVERRIDE(kflag) \
if (overrides & kflag) { \ if (overrides & kflag) { \
result &= ~kflag; \
result |= (own_flags & kflag); \ result |= (own_flags & kflag); \
} \
else { \
result |= (base_flags & kflag); \
} }
/* Apply the flags one by one... /* Apply the flags one by one...
* (See rna_def_common_keying_flags() for the supported flags) * (See rna_def_common_keying_flags() for the supported flags)
*/ */

View File

@@ -61,6 +61,7 @@ typedef struct ActKeyColumn {
/* keyframe info */ /* keyframe info */
char key_type; /* eBezTripe_KeyframeType */ char key_type; /* eBezTripe_KeyframeType */
char ipo_type; /* eKeyframeInterpolationDrawOpts */
short sel; short sel;
float cfra; float cfra;
@@ -108,8 +109,22 @@ typedef enum eKeyframeShapeDrawOpts {
KEYFRAME_SHAPE_BOTH KEYFRAME_SHAPE_BOTH
} eKeyframeShapeDrawOpts; } eKeyframeShapeDrawOpts;
/* interpolation type */
typedef enum eKeyframeInterpolationDrawOpts {
/* don't draw */
KEYFRAME_IPOTYPE_NONE = 0,
/* various marks */
KEYFRAME_IPOTYPE_CONST,
KEYFRAME_IPOTYPE_LINEAR,
KEYFRAME_IPOTYPE_EASING,
KEYFRAME_IPOTYPE_BEZ_AUTO_CLAMP,
KEYFRAME_IPOTYPE_BEZ_AUTO,
KEYFRAME_IPOTYPE_BEZ_VECTOR,
KEYFRAME_IPOTYPE_BEZ_MANUAL,
} eKeyframeInterpolationDrawOpts;
/* draw simple diamond-shape keyframe (with OpenGL) */ /* draw simple diamond-shape keyframe (with OpenGL) */
void draw_keyframe_shape(float x, float y, float xscale, float hsize, short sel, short key_type, short mode, float alpha); void draw_keyframe_shape(float x, float y, float xscale, float hsize, short sel, short key_type, short mode, float alpha, short ipo_type);
/* ******************************* Methods ****************************** */ /* ******************************* Methods ****************************** */

View File

@@ -282,7 +282,7 @@ bool keyframe_region_circle_test(
/* Destructive Editing API (keyframes_general.c) */ /* Destructive Editing API (keyframes_general.c) */
void delete_fcurve_key(struct FCurve *fcu, int index, bool do_recalc); void delete_fcurve_key(struct FCurve *fcu, int index, bool do_recalc);
bool delete_fcurve_keys(struct FCurve *fcu); bool delete_fcurve_keys(struct FCurve *fcu, bool resize_handles);
void clear_fcurve_keys(struct FCurve *fcu); void clear_fcurve_keys(struct FCurve *fcu);
void duplicate_fcurve_keys(struct FCurve *fcu); void duplicate_fcurve_keys(struct FCurve *fcu);

View File

@@ -256,7 +256,7 @@ static void vicon_keytype_draw_wrapper(int x, int y, int w, int h, float alpha,
* - yscale: 0.3 * h (found out experimentally... dunno why!) * - yscale: 0.3 * h (found out experimentally... dunno why!)
* - sel: true (so that "keyframe" state shows the iconic yellow icon) * - sel: true (so that "keyframe" state shows the iconic yellow icon)
*/ */
draw_keyframe_shape(xco, yco, 1.0f, 0.3f * h, true, key_type, KEYFRAME_SHAPE_BOTH, alpha); draw_keyframe_shape(xco, yco, 1.0f, 0.3f * h, true, key_type, KEYFRAME_SHAPE_BOTH, alpha, KEYFRAME_IPOTYPE_NONE);
UI_Theme_Restore(&theme_state); UI_Theme_Restore(&theme_state);
} }

View File

@@ -688,6 +688,9 @@ static void insert_action_keys(bAnimContext *ac, short mode)
/* init keyframing flag */ /* init keyframing flag */
flag = ANIM_get_keyframing_flags(scene, 1); flag = ANIM_get_keyframing_flags(scene, 1);
/* ignore cycle aware setting in action editor for now */
flag &= ~INSERTKEY_CYCLE_AWARE;
/* insert keyframes */ /* insert keyframes */
for (ale = anim_data.first; ale; ale = ale->next) { for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ANIM_nla_mapping_get(ac, ale); AnimData *adt = ANIM_nla_mapping_get(ac, ale);
@@ -906,7 +909,7 @@ static bool delete_action_keys(bAnimContext *ac)
AnimData *adt = ale->adt; AnimData *adt = ale->adt;
/* delete selected keyframes only */ /* delete selected keyframes only */
changed = delete_fcurve_keys(fcu); changed = delete_fcurve_keys(fcu, true);
/* Only delete curve too if it won't be doing anything anymore */ /* Only delete curve too if it won't be doing anything anymore */
if ((fcu->totvert == 0) && (list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE) == 0)) { if ((fcu->totvert == 0) && (list_has_suitable_fmodifier(&fcu->modifiers, 0, FMI_TYPE_GENERATE_CURVE) == 0)) {

View File

@@ -550,6 +550,9 @@ static void insert_graph_keys(bAnimContext *ac, eGraphKeys_InsertKey_Types mode)
/* init keyframing flag */ /* init keyframing flag */
flag = ANIM_get_keyframing_flags(scene, 1); flag = ANIM_get_keyframing_flags(scene, 1);
/* ignore cycle aware setting in graph editor for now */
flag &= ~INSERTKEY_CYCLE_AWARE;
/* insert keyframes */ /* insert keyframes */
if (mode & GRAPHKEYS_INSERTKEY_CURSOR) { if (mode & GRAPHKEYS_INSERTKEY_CURSOR) {
@@ -1014,7 +1017,7 @@ static bool delete_graph_keys(bAnimContext *ac)
bool changed; bool changed;
/* delete selected keyframes only */ /* delete selected keyframes only */
changed = delete_fcurve_keys(fcu); changed = delete_fcurve_keys(fcu, true);
if (changed) { if (changed) {
ale->update |= ANIM_UPDATE_DEFAULT; ale->update |= ANIM_UPDATE_DEFAULT;

View File

@@ -139,7 +139,7 @@ static void nla_action_draw_keyframes(AnimData *adt, bAction *act, View2D *v2d,
* - size is 3.0f which is smaller than the editable keyframes, so that there is a distinction * - size is 3.0f which is smaller than the editable keyframes, so that there is a distinction
*/ */
for (ak = keys.first; ak; ak = ak->next) for (ak = keys.first; ak; ak = ak->next)
draw_keyframe_shape(ak->cfra, y, xscale, 3.0f, 0, ak->key_type, KEYFRAME_SHAPE_FRAME, 1.0f); draw_keyframe_shape(ak->cfra, y, xscale, 3.0f, 0, ak->key_type, KEYFRAME_SHAPE_FRAME, 1.0f, KEYFRAME_IPOTYPE_NONE);
/* free icons */ /* free icons */
BLI_dlrbTree_free(&keys); BLI_dlrbTree_free(&keys);

View File

@@ -852,6 +852,7 @@ typedef enum eInsertKeyFlags {
* Used by copy/paste code. */ * Used by copy/paste code. */
INSERTKEY_OVERWRITE_FULL = (1<<7), INSERTKEY_OVERWRITE_FULL = (1<<7),
INSERTKEY_DRIVER = (1<<8), /* for driver FCurves, use driver's "input" value - for easier corrective driver setup */ INSERTKEY_DRIVER = (1<<8), /* for driver FCurves, use driver's "input" value - for easier corrective driver setup */
INSERTKEY_CYCLE_AWARE = (1<<9), /* for cyclic FCurves, adjust key timing to preserve the cycle period and flow */
} eInsertKeyFlags; } eInsertKeyFlags;
/* ************************************************ */ /* ************************************************ */

View File

@@ -744,6 +744,7 @@ typedef enum eAutokey_Flag {
/* toolsettings->autokey_flag */ /* toolsettings->autokey_flag */
AUTOKEY_FLAG_ONLYKEYINGSET = (1 << 6), AUTOKEY_FLAG_ONLYKEYINGSET = (1 << 6),
AUTOKEY_FLAG_NOWARNING = (1 << 7), AUTOKEY_FLAG_NOWARNING = (1 << 7),
AUTOKEY_FLAG_CYCLEAWARE = (1 << 8),
ANIMRECORD_FLAG_WITHNLA = (1 << 10), ANIMRECORD_FLAG_WITHNLA = (1 << 10),
} eAutokey_Flag; } eAutokey_Flag;

View File

@@ -38,6 +38,7 @@
#include "BLT_translation.h" #include "BLT_translation.h"
#include "BKE_action.h" #include "BKE_action.h"
#include "BKE_fcurve.h"
#include "RNA_access.h" #include "RNA_access.h"
#include "RNA_define.h" #include "RNA_define.h"
@@ -394,6 +395,13 @@ static void rna_FCurve_RnaPath_set(PointerRNA *ptr, const char *value)
fcu->rna_path = NULL; fcu->rna_path = NULL;
} }
static int rna_FCurve_is_cyclic_get(PointerRNA *ptr)
{
FCurve *fcu = (FCurve *)ptr->data;
return BKE_fcurve_is_cyclic(fcu);
}
static void rna_FCurve_group_set(PointerRNA *ptr, PointerRNA value) static void rna_FCurve_group_set(PointerRNA *ptr, PointerRNA value)
{ {
ID *pid = (ID *)ptr->id.data; ID *pid = (ID *)ptr->id.data;
@@ -1986,6 +1994,12 @@ static void rna_def_fcurve(BlenderRNA *brna)
"when evaluating"); "when evaluating");
RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, NULL); RNA_def_property_update(prop, NC_ANIMATION | ND_KEYFRAME_PROP, NULL);
prop = RNA_def_property(srna, "is_cyclic", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop, "rna_FCurve_is_cyclic_get", NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(prop, "Is Cyclic",
"True when the curve is a simple infinite cycle because of the Cycles modifier");
/* Collections */ /* Collections */
prop = RNA_def_property(srna, "sampled_points", PROP_COLLECTION, PROP_NONE); prop = RNA_def_property(srna, "sampled_points", PROP_COLLECTION, PROP_NONE);
RNA_def_property_collection_sdna(prop, NULL, "fpt", "totvert"); RNA_def_property_collection_sdna(prop, NULL, "fpt", "totvert");

View File

@@ -2843,7 +2843,13 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Auto Keyframe Insert Keying Set", RNA_def_property_ui_text(prop, "Auto Keyframe Insert Keying Set",
"Automatic keyframe insertion using active Keying Set only"); "Automatic keyframe insertion using active Keying Set only");
RNA_def_property_ui_icon(prop, ICON_KEYINGSET, 0); RNA_def_property_ui_icon(prop, ICON_KEYINGSET, 0);
prop = RNA_def_property(srna, "use_keyframe_cycle_aware", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "autokey_flag", AUTOKEY_FLAG_CYCLEAWARE);
RNA_def_property_ui_text(prop, "Cycle-Aware Keying",
"Keyframe insertion takes care to preserve Cyclic F-Curve period and continuity");
RNA_def_property_ui_icon(prop, ICON_PROP_CON, 0);
/* Keyframing */ /* Keyframing */
prop = RNA_def_property(srna, "keyframe_type", PROP_ENUM, PROP_NONE); prop = RNA_def_property(srna, "keyframe_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "keyframe_type"); RNA_def_property_enum_sdna(prop, NULL, "keyframe_type");