Animation: Gaussian Smooth operator for Graph Editor #105635
|
@ -394,20 +394,28 @@ void blend_to_default_fcurve(PointerRNA *id_ptr, FCurve *fcu, const float factor
|
|||
}
|
||||
/* ---------------- */
|
||||
|
||||
void get_1d_gauss_kernel(const int filter_width, const float sigma, double *kernel)
|
||||
void get_1d_gauss_kernel(const float sigma, const int kernel_size, double *r_kernel)
|
||||
|
||||
{
|
||||
dr.sybren marked this conversation as resolved
Sybren A. Stüvel
commented
You could add
to document the assumptions of the code. If You could add
```c
BLI_assert(sigma > 0.0f);
BLI_assert(kernel_size > 0);
```
to document the assumptions of the code. If `sigma == 0` it'll cause some nice division issues.
|
||||
double norm = 1.0 / (M_2_SQRTPI * sigma);
|
||||
Sybren A. Stüvel
commented
I think you can remove I think you can remove `norm` from the calculations completely. Since you normalise the end result anyway, this constant factor doesn't have an impact on the final result.
|
||||
double sig_sq = 2.0 * sigma * sigma;
|
||||
double sigma_sq = 2.0 * sigma * sigma;
|
||||
Sybren A. Stüvel
commented
`const`
|
||||
double sum = 0.0;
|
||||
|
||||
for (int i = -filter_width; i <= filter_width; i++) {
|
||||
kernel[i + filter_width] = norm * exp(-i * i / sig_sq);
|
||||
sum += kernel[i + filter_width];
|
||||
for (int i = 0; i < kernel_size; i++) {
|
||||
const double normalized_index = (double)i / (kernel_size - 1);
|
||||
r_kernel[i] = norm * exp(-normalized_index * normalized_index / sigma_sq);
|
||||
if (i == 0) {
|
||||
sum += r_kernel[i];
|
||||
}
|
||||
else {
|
||||
/* We only calculate half the kernel,
|
||||
* the normalization needs to take that into account. */
|
||||
sum += r_kernel[i] * 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Normalize kernel values. */
|
||||
for (int i = 0; i < filter_width * 2 + 1; i++) {
|
||||
kernel[i] = kernel[i] / sum;
|
||||
for (int i = 0; i < kernel_size; i++) {
|
||||
r_kernel[i] = r_kernel[i] / sum;
|
||||
dr.sybren marked this conversation as resolved
Sybren A. Stüvel
commented
```c
r_kernel[i] /= sum;
```
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -415,17 +423,17 @@ void smooth_fcurve_segment(FCurve *fcu,
|
|||
FCurveSegment *segment,
|
||||
float *samples,
|
||||
const float factor,
|
||||
const int filter_order,
|
||||
const int kernel_size,
|
||||
double *kernel)
|
||||
{
|
||||
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
|
||||
dr.sybren marked this conversation as resolved
Sybren A. Stüvel
commented
`segment->start_index + segment->length` and `fcu->bezt[segment->start_index].vec[1][0]` don't change during the loop, so you can store them in a constant outside of the loop and use that instead.
|
||||
const int sample_index = (int)(fcu->bezt[i].vec[1][0] -
|
||||
fcu->bezt[segment->start_index].vec[1][0]) +
|
||||
filter_order;
|
||||
kernel_size;
|
||||
double filter_result = 0;
|
||||
/* Apply the kernel. */
|
||||
for (int j = -filter_order; j <= filter_order; j++) {
|
||||
filter_result += samples[sample_index + j] * kernel[filter_order + j];
|
||||
for (int j = -kernel_size; j <= kernel_size; j++) {
|
||||
Sybren A. Stüvel
commented
This comment should swap with the line above it, as This comment should swap with the line above it, as `double filter_result = samples[sample_index] * kernel[0];` is already part of applying the kernel.
|
||||
filter_result += samples[sample_index + j] * kernel[abs(j)];
|
||||
Sybren A. Stüvel
commented
What do you think would be faster? The current approach? Or halving the loop and avoiding the call to
What do you think would be faster? The current approach? Or halving the loop and avoiding the call to `abs(j)`?
```c
double filter_result = samples[sample_index] * 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;
}
```
|
||||
}
|
||||
fcu->bezt[i].vec[1][1] = interpf((float)filter_result, samples[sample_index], factor);
|
||||
}
|
||||
|
|
|
@ -424,12 +424,16 @@ void blend_to_neighbor_fcurve_segment(struct FCurve *fcu,
|
|||
struct FCurveSegment *segment,
|
||||
float factor);
|
||||
void breakdown_fcurve_segment(struct FCurve *fcu, struct FCurveSegment *segment, float factor);
|
||||
void get_1d_gauss_kernel(int filter_width, const float sigma, double *kernel);
|
||||
/** Get a 1D gauss kernel. Since the kernel is symmetrical, only calculates the positive side.
|
||||
* \param sigma The shape of the gauss distribution.
|
||||
* \param kernel_size How long the kernel array is.
|
||||
*/
|
||||
void get_1d_gauss_kernel(const float sigma, int kernel_size, double *r_kernel);
|
||||
void smooth_fcurve_segment(struct FCurve *fcu,
|
||||
struct FCurveSegment *segment,
|
||||
float *samples,
|
||||
float factor,
|
||||
int filter_order,
|
||||
int kernel_size,
|
||||
double *kernel);
|
||||
void ease_fcurve_segment(struct FCurve *fcu, struct FCurveSegment *segment, float factor);
|
||||
bool decimate_fcurve(struct bAnimListElem *ale, float remove_ratio, float error_sq_max);
|
||||
|
|
|
@ -1065,12 +1065,11 @@ static void gauss_smooth_graph_keys(bAnimContext *ac,
|
|||
ListBase anim_data = {NULL, NULL};
|
||||
ANIM_animdata_filter(ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, ac->datatype);
|
||||
|
||||
bAnimListElem *ale;
|
||||
const int kernel_size = filter_width + 1;
|
||||
double *kernel = MEM_callocN(sizeof(double) * kernel_size, "Gauss Kernel");
|
||||
get_1d_gauss_kernel(sigma, kernel_size, kernel);
|
||||
|
||||
double *kernel = MEM_callocN(sizeof(double) * (filter_width * 2 + 1), "Gauss Kernel");
|
||||
get_1d_gauss_kernel(filter_width, sigma, kernel);
|
||||
|
||||
for (ale = anim_data.first; ale; ale = ale->next) {
|
||||
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
|
||||
FCurve *fcu = (FCurve *)ale->key_data;
|
||||
ListBase segments = find_fcurve_segments(fcu);
|
||||
|
||||
|
@ -1078,7 +1077,7 @@ static void gauss_smooth_graph_keys(bAnimContext *ac,
|
|||
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]) +
|
||||
(filter_width * 2) + 1;
|
||||
(filter_width * 2 + 1);
|
||||
float *samples = MEM_callocN(sizeof(float) * sample_count, "Smooth FCurve Op Samples");
|
||||
sample_fcurve_segment(fcu, left_bezt.vec[1][0] - filter_width, samples, sample_count);
|
||||
smooth_fcurve_segment(fcu, segment, samples, factor, filter_width, kernel);
|
||||
|
@ -1094,6 +1093,38 @@ static void gauss_smooth_graph_keys(bAnimContext *ac,
|
|||
ANIM_animdata_freelist(&anim_data);
|
||||
}
|
||||
|
||||
static void gauss_smooth_modal_update(bContext *C, wmOperator *op)
|
||||
{
|
||||
tGraphSliderOp *gso = op->customdata;
|
||||
|
||||
ease_draw_status_header(C, gso);
|
||||
|
||||
/* Reset keyframes to the state at invoke. */
|
||||
reset_bezts(gso);
|
||||
const float factor = slider_factor_get_and_remember(op);
|
||||
const float sigma = RNA_float_get(op->ptr, "sigma");
|
||||
const int filter_width = RNA_int_get(op->ptr, "filter_width");
|
||||
gauss_smooth_graph_keys(&gso->ac, factor, sigma, filter_width);
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
|
||||
}
|
||||
|
||||
static int gauss_smooth_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
const int invoke_result = graph_slider_invoke(C, op, event);
|
||||
|
||||
if (invoke_result == OPERATOR_CANCELLED) {
|
||||
return invoke_result;
|
||||
}
|
||||
|
||||
tGraphSliderOp *gso = op->customdata;
|
||||
gso->modal_update = gauss_smooth_modal_update;
|
||||
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
||||
ED_slider_allow_overshoot_set(gso->slider, false);
|
||||
ease_draw_status_header(C, gso);
|
||||
|
||||
return invoke_result;
|
||||
}
|
||||
|
||||
static int gauss_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
bAnimContext ac;
|
||||
|
@ -1120,8 +1151,8 @@ void GRAPH_OT_gauss_smooth(wmOperatorType *ot)
|
|||
ot->description = "Smooth the curve using a Gauss filter";
|
||||
|
||||
/* API callbacks. */
|
||||
/* ot->invoke = fft_invoke; */
|
||||
/* ot->modal = fft_modal; */
|
||||
ot->invoke = gauss_smooth_invoke;
|
||||
ot->modal = graph_slider_modal;
|
||||
ot->exec = gauss_exec;
|
||||
ot->poll = graphop_editable_keyframes_poll;
|
||||
|
||||
|
@ -1140,7 +1171,7 @@ void GRAPH_OT_gauss_smooth(wmOperatorType *ot)
|
|||
|
||||
RNA_def_float(ot->srna,
|
||||
"sigma",
|
||||
2.0f,
|
||||
0.33f,
|
||||
0.001f,
|
||||
FLT_MAX,
|
||||
"Sigma",
|
||||
|
@ -1150,7 +1181,7 @@ void GRAPH_OT_gauss_smooth(wmOperatorType *ot)
|
|||
|
||||
RNA_def_int(ot->srna,
|
||||
"filter_width",
|
||||
8,
|
||||
6,
|
||||
1,
|
||||
64,
|
||||
"Filter Width",
|
||||
|
|
Loading…
Reference in New Issue
For non-static functions, I think it might be better to move the
ED_anim_...
prefixes.