diff options
author | Sybren A. Stüvel <sybren@blender.org> | 2020-11-23 14:48:04 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@blender.org> | 2020-11-23 14:48:04 +0300 |
commit | e4ca1fc4ea43f795441a319ea96b63a58553f070 (patch) | |
tree | 2dcb89adb56958fc26d41ceac51af6dc9dd72814 /source/blender/editors/space_graph/graph_edit.c | |
parent | 1318eaf166947e080c05ed689dbc295817496f1a (diff) |
Animation: New Euler filter implementation
This new discontinuity filter performs actions on the entire Euler
rotation, rather than only on the individual X/Y/Z channels. This makes
it fix a wider range of discontinuities, for example those in T52744.
The filter now runs twice on the selected channels, in this order:
- New: Convert X+Y+Z rotation to matrix, then back to Euler angles.
- Old: Add/remove factors of 360° to minimize jumps.
The messaging is streamlined; it now reports how many channels were
filtered, and only warns (instead of errors) when there was an actual
problem with the selected channels (like selecting three or more
channels, but without X/Y/Z triplet).
A new kernel function `BKE_fcurve_keyframe_move_value_with_handles()` is
introduced, to make it possible to move a keyframe's value and move its
handles at the same time.
Manifest Task: T52744
Reviewed By: looch
Differential Revision: https://developer.blender.org/D9602
Diffstat (limited to 'source/blender/editors/space_graph/graph_edit.c')
-rw-r--r-- | source/blender/editors/space_graph/graph_edit.c | 238 |
1 files changed, 171 insertions, 67 deletions
diff --git a/source/blender/editors/space_graph/graph_edit.c b/source/blender/editors/space_graph/graph_edit.c index 09440ee7d98..bcd3adec1ab 100644 --- a/source/blender/editors/space_graph/graph_edit.c +++ b/source/blender/editors/space_graph/graph_edit.c @@ -2610,6 +2610,14 @@ typedef struct tEulerFilter { const char *rna_path; } tEulerFilter; +static bool keyframe_time_differs(BezTriple *keyframes[3]) +{ + const float precision = 1e-5; + return fabs(keyframes[0]->vec[1][0] - keyframes[1]->vec[1][0]) > precision || + fabs(keyframes[1]->vec[1][0] - keyframes[2]->vec[1][0]) > precision || + fabs(keyframes[0]->vec[1][0] - keyframes[2]->vec[1][0]) > precision; +} + /* Find groups of `rotation_euler` channels. */ static ListBase /*tEulerFilter*/ euler_filter_group_channels( const ListBase /*bAnimListElem*/ *anim_data, ReportList *reports, int *r_num_groups) @@ -2666,68 +2674,151 @@ static ListBase /*tEulerFilter*/ euler_filter_group_channels( return euler_groups; } -static int euler_filter_perform_filter(ListBase /*tEulerFilter*/ *eulers, ReportList *reports) +/* Perform discontinuity filter based on conversion to matrix and back. + * Return true if the curves were filtered (which may have been a no-op), false otherwise. */ +static bool euler_filter_multi_channel(tEulerFilter *euf, ReportList *reports) +{ + /* Sanity check: ensure that there are enough F-Curves to work on in this group. */ + if (ELEM(NULL, euf->fcurves[0], euf->fcurves[1], euf->fcurves[2])) { + /* Report which components are missing. */ + BKE_reportf(reports, + RPT_INFO, + "Missing %s%s%s component(s) of euler rotation for ID='%s' and RNA-Path='%s'", + (euf->fcurves[0] == NULL) ? "X" : "", + (euf->fcurves[1] == NULL) ? "Y" : "", + (euf->fcurves[2] == NULL) ? "Z" : "", + euf->id->name, + euf->rna_path); + return false; + } + + FCurve *fcu_rot_x = euf->fcurves[0]; + FCurve *fcu_rot_y = euf->fcurves[1]; + FCurve *fcu_rot_z = euf->fcurves[2]; + if (fcu_rot_x->totvert != fcu_rot_y->totvert || fcu_rot_y->totvert != fcu_rot_z->totvert) { + BKE_reportf(reports, + RPT_INFO, + "XYZ rotations not equally keyed for ID='%s' and RNA-Path='%s'", + euf->id->name, + euf->rna_path); + return false; + } + + if (fcu_rot_x->totvert < 2) { + /* Empty curves and single keyframes are trivially "filtered". */ + return false; + } + + float filtered_euler[3] = { + fcu_rot_x->bezt[0].vec[1][1], + fcu_rot_y->bezt[0].vec[1][1], + fcu_rot_z->bezt[0].vec[1][1], + }; + + for (int keyframe_index = 1; keyframe_index < fcu_rot_x->totvert; ++keyframe_index) { + BezTriple *keyframes[3] = { + &fcu_rot_x->bezt[keyframe_index], + &fcu_rot_y->bezt[keyframe_index], + &fcu_rot_z->bezt[keyframe_index], + }; + + if (keyframe_time_differs(keyframes)) { + /* The X-coordinates of the keyframes are different, so we cannot correct this key. */ + continue; + } + + const float unfiltered_euler[3] = { + keyframes[0]->vec[1][1], + keyframes[1]->vec[1][1], + keyframes[2]->vec[1][1], + }; + + /* The conversion back from matrix to Euler angles actually performs the filtering. */ + float matrix[3][3]; + eul_to_mat3(matrix, unfiltered_euler); + mat3_normalized_to_compatible_eul(filtered_euler, filtered_euler, matrix); + + /* TODO(Sybren): it might be a nice touch to compare `filtered_euler` with `unfiltered_euler`, + * to see if there was actually a change. This could improve reporting for the artist. */ + + BKE_fcurve_keyframe_move_value_with_handles(keyframes[0], filtered_euler[0]); + BKE_fcurve_keyframe_move_value_with_handles(keyframes[1], filtered_euler[1]); + BKE_fcurve_keyframe_move_value_with_handles(keyframes[2], filtered_euler[2]); + } + + return true; +} + +/* Remove 360-degree flips from a single FCurve. + * Return true if the curve was modified, false otherwise. */ +static bool euler_filter_single_channel(FCurve *fcu) { - int failed = 0; + /* Simple method: just treat any difference between keys of greater than 180 degrees as being a + * flip. */ + BezTriple *bezt, *prev; + uint i; - LISTBASE_FOREACH (tEulerFilter *, euf, eulers) { - /* Sanity check: ensure that there are enough F-Curves to work on in this group. */ - /* TODO: also enforce assumption that there be a full set of keyframes - * at each position by ensuring that totvert counts are same? (Joshua Leung 2011) */ - if (ELEM(NULL, euf->fcurves[0], euf->fcurves[1], euf->fcurves[2])) { - /* Report which components are missing. */ - BKE_reportf(reports, - RPT_WARNING, - "Missing %s%s%s component(s) of euler rotation for ID='%s' and RNA-Path='%s'", - (euf->fcurves[0] == NULL) ? "X" : "", - (euf->fcurves[1] == NULL) ? "Y" : "", - (euf->fcurves[2] == NULL) ? "Z" : "", - euf->id->name, - euf->rna_path); - - /* Keep track of number of failed sets, and carry on to next group. */ - failed++; + /* Skip if not enough verts to do a decent analysis. */ + if (fcu->totvert <= 2) { + return false; + } + + /* Prev follows bezt, bezt = "current" point to be fixed. */ + /* Our method depends on determining a "difference" from the previous vert. */ + bool is_modified = false; + for (i = 1, prev = fcu->bezt, bezt = fcu->bezt + 1; i < fcu->totvert; i++, prev = bezt++) { + const float sign = (prev->vec[1][1] > bezt->vec[1][1]) ? 1.0f : -1.0f; + + /* >= 180 degree flip? */ + if ((sign * (prev->vec[1][1] - bezt->vec[1][1])) < (float)M_PI) { continue; } - /* Simple method: just treat any difference between - * keys of greater than 180 degrees as being a flip. */ - /* FIXME: there are more complicated methods that - * will be needed to fix more cases than just some */ - for (int f = 0; f < 3; f++) { - FCurve *fcu = euf->fcurves[f]; - BezTriple *bezt, *prev; - uint i; - - /* Skip if not enough vets to do a decent analysis of.... */ - if (fcu->totvert <= 2) { - continue; - } + /* 360 degrees to add/subtract frame value until difference + * is acceptably small that there's no more flip. */ + const float fac = sign * 2.0f * (float)M_PI; - /* Prev follows bezt, bezt = "current" point to be fixed. */ - /* Our method depends on determining a "difference" from the previous vert. */ - for (i = 1, prev = fcu->bezt, bezt = fcu->bezt + 1; i < fcu->totvert; i++, prev = bezt++) { - const float sign = (prev->vec[1][1] > bezt->vec[1][1]) ? 1.0f : -1.0f; + while ((sign * (prev->vec[1][1] - bezt->vec[1][1])) >= (float)M_PI) { + bezt->vec[0][1] += fac; + bezt->vec[1][1] += fac; + bezt->vec[2][1] += fac; + } - /* >= 180 degree flip? */ - if ((sign * (prev->vec[1][1] - bezt->vec[1][1])) < (float)M_PI) { - continue; - } + is_modified = true; + } - /* 360 degrees to add/subtract frame value until difference - * is acceptably small that there's no more flip. */ - const float fac = sign * 2.0f * (float)M_PI; + return is_modified; +} - while ((sign * (prev->vec[1][1] - bezt->vec[1][1])) >= (float)M_PI) { - bezt->vec[0][1] += fac; - bezt->vec[1][1] += fac; - bezt->vec[2][1] += fac; - } +static void euler_filter_perform_filter(ListBase /*tEulerFilter*/ *eulers, + ReportList *reports, + int *r_curves_filtered, + int *r_curves_seen) +{ + *r_curves_filtered = 0; + *r_curves_seen = 0; + + LISTBASE_FOREACH (tEulerFilter *, euf, eulers) { + int curves_filtered_this_group = 0; + + if (euler_filter_multi_channel(euf, reports)) { + curves_filtered_this_group = 3; + } + + for (int channel_index = 0; channel_index < 3; channel_index++) { + FCurve *fcu = euf->fcurves[channel_index]; + if (fcu == NULL) { + continue; + } + ++*r_curves_seen; + + if (euler_filter_single_channel(fcu)) { + ++curves_filtered_this_group; } } - } - return failed; + *r_curves_filtered += min_ii(3, curves_filtered_this_group); + } } static int graphkeys_euler_filter_exec(bContext *C, wmOperator *op) @@ -2764,30 +2855,43 @@ static int graphkeys_euler_filter_exec(bContext *C, wmOperator *op) /* Step 2: go through each set of curves, processing the values at each keyframe. * - It is assumed that there must be a full set of keyframes at each keyframe position. */ - const int failed = euler_filter_perform_filter(&eulers, op->reports); - BLI_freelistN(&eulers); + int curves_filtered; + int curves_seen; + euler_filter_perform_filter(&eulers, op->reports, &curves_filtered, &curves_seen); + BLI_freelistN(&eulers); ANIM_animdata_update(&ac, &anim_data); ANIM_animdata_freelist(&anim_data); - /* Updates + finishing warnings. */ - if (failed == groups) { - BKE_report( - op->reports, - RPT_ERROR, - "No Euler Rotations could be corrected, ensure each rotation has keys for all components, " - "and that F-Curves for these are in consecutive XYZ order and selected"); + if (curves_filtered == 0) { + if (curves_seen < 3) { + /* Showing the entire error message makes no sense when the artist is only trying to filter + * one or two curves. */ + BKE_report(op->reports, RPT_WARNING, "No Euler Rotations could be corrected."); + } + else { + BKE_report(op->reports, + RPT_ERROR, + "No Euler Rotations could be corrected, ensure each rotation has keys for all " + "components, " + "and that F-Curves for these are in consecutive XYZ order and selected"); + } return OPERATOR_CANCELLED; } - if (failed) { - BKE_report( - op->reports, - RPT_ERROR, - "Some Euler Rotations could not be corrected due to missing/unselected/out-of-order " - "F-Curves, " - "ensure each rotation has keys for all components, and that F-Curves for these are in " - "consecutive XYZ order and selected"); + if (curves_filtered != curves_seen) { + BLI_assert(curves_filtered < curves_seen); + BKE_reportf(op->reports, + RPT_INFO, + "%d of %d rotation channels were filtered. See the Info window for details.", + curves_filtered, + curves_seen); + } + else if (curves_seen == 1) { + BKE_report(op->reports, RPT_INFO, "The rotation channel was filtered."); + } + else { + BKE_reportf(op->reports, RPT_INFO, "All %d rotation channels were filtered.", curves_seen); } /* Set notifier that keyframes have changed. */ |