Fix #140669: Graph Editor Gaussian smoothing causing artifacts for keys on subframes
This adds to the fix done with #110059. With the changes of this patch, the smoothing still happens ONLY on full frames. Any subframe data is linearly interpolated to reduce the stepping seen before this PR. As a side effect, the operator now has to store the original y values of the keys in question. This is needed for correct blending, whereas before it was assumed that the samples contain the original y values. No changes to the butterworth filter, because that already has a property to increase the sample rate for sub-frame data. Pull Request: https://projects.blender.org/blender/blender/pulls/140928
This commit is contained in:
committed by
Christoph Lendenfeld
parent
cac2806ee2
commit
8da357f1ea
@@ -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<float *>(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);
|
||||
}
|
||||
/* ---------------- */
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<float>(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<tFCurveSegmentLink>("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<float>(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);
|
||||
|
||||
Reference in New Issue
Block a user