diff options
Diffstat (limited to 'source/blender/editors/space_graph')
-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. */ |