diff --git a/source/blender/editors/animation/keyframes_general.cc b/source/blender/editors/animation/keyframes_general.cc index 22ab574a687..2bb5009570c 100644 --- a/source/blender/editors/animation/keyframes_general.cc +++ b/source/blender/editors/animation/keyframes_general.cc @@ -635,27 +635,46 @@ void ED_ANIM_get_1d_gauss_kernel(const float sigma, const int kernel_size, doubl void smooth_fcurve_segment(FCurve *fcu, FCurveSegment *segment, + const float *original_values, float *samples, + const int sample_count, const float factor, const int kernel_size, const double *kernel) { const int segment_end_index = segment->start_index + segment->length; const float segment_start_x = fcu->bezt[segment->start_index].vec[1][0]; - for (int i = segment->start_index; i < segment_end_index; i++) { - /* Using round() instead of (int). The latter would create stepping on x-values that are just - * below a full frame. */ - const int sample_index = round(fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size; + float *filtered_samples = static_cast(MEM_dupallocN(samples)); + for (int i = kernel_size; i < sample_count - kernel_size; i++) { /* Apply the kernel. */ - double filter_result = samples[sample_index] * kernel[0]; + double filter_result = samples[i] * kernel[0]; for (int j = 1; j <= kernel_size; j++) { const double kernel_value = kernel[j]; - filter_result += samples[sample_index + j] * kernel_value; - filter_result += samples[sample_index - j] * kernel_value; + filter_result += samples[i + j] * kernel_value; + filter_result += samples[i - j] * kernel_value; } - const float key_y_value = interpf(float(filter_result), samples[sample_index], factor); + filtered_samples[i] = filter_result; + } + + for (int i = segment->start_index; i < segment_end_index; i++) { + const float sample_index_f = (fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size; + /* Using round() instead of (int). The latter would create stepping on x-values that are just + * below a full frame. */ + const int sample_index = round(sample_index_f); + /* Sampling the two closest indices to support subframe keys. This can end up being the same + * index as sample_index, in which case the interpolation will happen between two identical + * values. */ + const int secondary_index = clamp_i( + sample_index + signum_i(sample_index_f - sample_index), 0, sample_count - 1); + + const float filter_result = interpf(filtered_samples[secondary_index], + filtered_samples[sample_index], + std::abs(sample_index_f - sample_index)); + const float key_y_value = interpf( + filter_result, original_values[i - segment->start_index], factor); BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value); } + MEM_freeN(filtered_samples); } /* ---------------- */ diff --git a/source/blender/editors/include/ED_keyframes_edit.hh b/source/blender/editors/include/ED_keyframes_edit.hh index c78ebde533b..ca55433ae7c 100644 --- a/source/blender/editors/include/ED_keyframes_edit.hh +++ b/source/blender/editors/include/ED_keyframes_edit.hh @@ -479,7 +479,9 @@ void butterworth_smooth_fcurve_segment(FCurve *fcu, ButterworthCoefficients *bw_coeff); void smooth_fcurve_segment(FCurve *fcu, FCurveSegment *segment, + const float *original_values, float *samples, + const int sample_count, float factor, int kernel_size, const double *kernel); diff --git a/source/blender/editors/space_graph/graph_slider_ops.cc b/source/blender/editors/space_graph/graph_slider_ops.cc index b7e55640be9..61efc93f4a1 100644 --- a/source/blender/editors/space_graph/graph_slider_ops.cc +++ b/source/blender/editors/space_graph/graph_slider_ops.cc @@ -1790,10 +1790,25 @@ struct tFCurveSegmentLink { tFCurveSegmentLink *next, *prev; FCurve *fcu; FCurveSegment *segment; - float *samples; /* Array of y-values of the FCurve segment. */ + /* Array of y-values. The length of the array equals the length of the + * segment. */ + float *original_y_values; + /* Array of y-values of the FCurve segment at regular intervals. */ + float *samples; int sample_count; }; +/* Allocates data that has to be freed after. */ +static float *back_up_key_y_values(const FCurveSegment *segment, const FCurve *fcu) +{ + float *original_y_values = MEM_calloc_arrayN(segment->length, + "Smooth FCurve original values"); + for (int i = 0; i < segment->length; i++) { + original_y_values[i] = fcu->bezt[i + segment->start_index].vec[1][1]; + } + return original_y_values; +} + static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso, const int filter_width, const float sigma) @@ -1816,6 +1831,7 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso, tFCurveSegmentLink *segment_link = MEM_callocN("FCurve Segment Link"); segment_link->fcu = fcu; segment_link->segment = segment; + segment_link->original_y_values = back_up_key_y_values(segment, fcu); BezTriple left_bezt = fcu->bezt[segment->start_index]; BezTriple right_bezt = fcu->bezt[segment->start_index + segment->length - 1]; const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) + @@ -1824,6 +1840,7 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso, blender::animrig::sample_fcurve_segment( fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count); segment_link->samples = samples; + segment_link->sample_count = sample_count; BLI_addtail(&segment_links, segment_link); } } @@ -1839,6 +1856,7 @@ static void gaussian_smooth_free_operator_data(void *operator_data) LISTBASE_FOREACH (tFCurveSegmentLink *, segment_link, &gauss_data->segment_links) { MEM_freeN(segment_link->samples); MEM_freeN(segment_link->segment); + MEM_freeN(segment_link->original_y_values); } MEM_freeN(gauss_data->kernel); BLI_freelistN(&gauss_data->segment_links); @@ -1865,7 +1883,9 @@ static void gaussian_smooth_modal_update(bContext *C, wmOperator *op) LISTBASE_FOREACH (tFCurveSegmentLink *, segment, &operator_data->segment_links) { smooth_fcurve_segment(segment->fcu, segment->segment, + segment->original_y_values, segment->samples, + segment->sample_count, factor, filter_width, operator_data->kernel); @@ -1923,10 +1943,13 @@ static void gaussian_smooth_graph_keys(bAnimContext *ac, const int sample_count = int(right_bezt.vec[1][0] - left_bezt.vec[1][0]) + (filter_width * 2 + 1); float *samples = MEM_calloc_arrayN(sample_count, "Smooth FCurve Op Samples"); + float *original_y_values = back_up_key_y_values(segment, fcu); blender::animrig::sample_fcurve_segment( fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count); - smooth_fcurve_segment(fcu, segment, samples, factor, filter_width, kernel); + smooth_fcurve_segment( + fcu, segment, original_y_values, samples, sample_count, factor, filter_width, kernel); MEM_freeN(samples); + MEM_freeN(original_y_values); } BLI_freelistN(&segments);