Animation: Gaussian Smooth operator for Graph Editor #105635
|
@ -330,7 +330,7 @@ class GRAPH_MT_slider(Menu):
|
|||
layout.operator("graph.blend_to_neighbor", text="Blend to Neighbor")
|
||||
layout.operator("graph.blend_to_default", text="Blend to Default Value")
|
||||
layout.operator("graph.ease", text="Ease")
|
||||
layout.operator("graph.gauss_smooth", text="Gauss Smooth")
|
||||
layout.operator("graph.gaussian_smooth", text="Smooth")
|
||||
|
||||
|
||||
class GRAPH_MT_view_pie(Menu):
|
||||
|
|
|
@ -394,15 +394,16 @@ void blend_to_default_fcurve(PointerRNA *id_ptr, FCurve *fcu, const float factor
|
|||
}
|
||||
/* ---------------- */
|
||||
|
||||
void get_1d_gauss_kernel(const float sigma, const int kernel_size, double *r_kernel)
|
||||
void ED_ANIM_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);
|
||||
double sigma_sq = 2.0 * sigma * sigma;
|
||||
BLI_assert(sigma > 0.0f);
|
||||
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.
|
||||
BLI_assert(kernel_size > 0);
|
||||
Sybren A. Stüvel
commented
`const`
|
||||
const double sigma_sq = 2.0 * sigma * sigma;
|
||||
double sum = 0.0;
|
||||
|
||||
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);
|
||||
r_kernel[i] = exp(-normalized_index * normalized_index / sigma_sq);
|
||||
if (i == 0) {
|
||||
sum += r_kernel[i];
|
||||
}
|
||||
|
@ -415,7 +416,7 @@ void get_1d_gauss_kernel(const float sigma, const int kernel_size, double *r_ker
|
|||
|
||||
/* Normalize kernel values. */
|
||||
for (int i = 0; i < kernel_size; i++) {
|
||||
dr.sybren marked this conversation as resolved
Sybren A. Stüvel
commented
```c
r_kernel[i] /= sum;
```
|
||||
r_kernel[i] = r_kernel[i] / sum;
|
||||
r_kernel[i] /= sum;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,14 +427,16 @@ void smooth_fcurve_segment(FCurve *fcu,
|
|||
const int kernel_size,
|
||||
double *kernel)
|
||||
{
|
||||
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.
|
||||
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
|
||||
const int sample_index = (int)(fcu->bezt[i].vec[1][0] -
|
||||
fcu->bezt[segment->start_index].vec[1][0]) +
|
||||
kernel_size;
|
||||
double filter_result = 0;
|
||||
const int segment_end_index = segment->start_index + segment->length;
|
||||
const int segment_start_x = fcu->bezt[segment->start_index].vec[1][0];
|
||||
for (int i = segment->start_index; i < segment_end_index; i++) {
|
||||
const int sample_index = (int)(fcu->bezt[i].vec[1][0] - segment_start_x) + kernel_size;
|
||||
double filter_result = samples[sample_index] * kernel[0];
|
||||
/* Apply the kernel. */
|
||||
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.
|
||||
for (int j = -kernel_size; j <= kernel_size; j++) {
|
||||
filter_result += samples[sample_index + j] * kernel[abs(j)];
|
||||
for (int j = 1; j <= kernel_size; 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;
}
```
|
||||
const double kernel_value = kernel[j];
|
||||
filter_result += samples[sample_index + j] * kernel_value;
|
||||
filter_result += samples[sample_index - j] * kernel_value;
|
||||
}
|
||||
const float key_y_value = interpf((float)filter_result, samples[sample_index], factor);
|
||||
move_key(&fcu->bezt[i], key_y_value);
|
||||
|
|
|
@ -429,7 +429,7 @@ void breakdown_fcurve_segment(struct FCurve *fcu, struct FCurveSegment *segment,
|
|||
* \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 ED_ANIM_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,
|
||||
|
|
|
@ -116,7 +116,7 @@ void GRAPH_OT_breakdown(struct wmOperatorType *ot);
|
|||
void GRAPH_OT_ease(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_decimate(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_blend_to_default(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_gauss_smooth(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_gaussian_smooth(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_sample(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_bake(struct wmOperatorType *ot);
|
||||
void GRAPH_OT_unbake(struct wmOperatorType *ot);
|
||||
|
|
|
@ -464,7 +464,7 @@ void graphedit_operatortypes(void)
|
|||
WM_operatortype_append(GRAPH_OT_breakdown);
|
||||
WM_operatortype_append(GRAPH_OT_ease);
|
||||
WM_operatortype_append(GRAPH_OT_blend_to_default);
|
||||
WM_operatortype_append(GRAPH_OT_gauss_smooth);
|
||||
WM_operatortype_append(GRAPH_OT_gaussian_smooth);
|
||||
WM_operatortype_append(GRAPH_OT_euler_filter);
|
||||
WM_operatortype_append(GRAPH_OT_delete);
|
||||
WM_operatortype_append(GRAPH_OT_duplicate);
|
||||
|
|
|
@ -1073,22 +1073,23 @@ typedef struct tGaussOperatorData {
|
|||
ListBase anim_data; /* bAnimListElem */
|
||||
} tGaussOperatorData;
|
||||
|
||||
/* Store data to smooth an FCurve segment. */
|
||||
Sybren A. Stüvel
commented
Document what this struct contains / what it's for. Document what this struct contains / what it's for.
|
||||
typedef struct tFCurveSegmentLink {
|
||||
struct tFCurveSegmentLink *prev, *next;
|
||||
FCurve *fcu;
|
||||
FCurveSegment *segment;
|
||||
float *samples;
|
||||
float *samples; /* Array of y-values of the FCurve segment. */
|
||||
} tFCurveSegmentLink;
|
||||
|
||||
static void gauss_smooth_allocate_operator_data(tGraphSliderOp *gso,
|
||||
const int filter_width,
|
||||
const float sigma)
|
||||
static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
|
||||
const int filter_width,
|
||||
const float sigma)
|
||||
{
|
||||
tGaussOperatorData *operator_data = MEM_callocN(sizeof(tGaussOperatorData),
|
||||
"tGaussOperatorData");
|
||||
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);
|
||||
ED_ANIM_get_1d_gauss_kernel(sigma, kernel_size, kernel);
|
||||
operator_data->kernel = kernel;
|
||||
|
||||
ListBase anim_data = {NULL, NULL};
|
||||
|
@ -1119,7 +1120,7 @@ static void gauss_smooth_allocate_operator_data(tGraphSliderOp *gso,
|
|||
gso->operator_data = operator_data;
|
||||
}
|
||||
|
||||
static void gauss_smooth_free_operator_data(void *operator_data)
|
||||
static void gaussian_smooth_free_operator_data(void *operator_data)
|
||||
{
|
||||
tGaussOperatorData *gauss_data = (tGaussOperatorData *)operator_data;
|
||||
LISTBASE_FOREACH (tFCurveSegmentLink *, segment_link, &gauss_data->segment_links) {
|
||||
|
@ -1132,7 +1133,7 @@ static void gauss_smooth_free_operator_data(void *operator_data)
|
|||
MEM_freeN(gauss_data);
|
||||
}
|
||||
|
||||
static void gauss_smooth_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
||||
static void gaussian_smooth_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
||||
{
|
||||
char status_str[UI_MAX_DRAW_STR];
|
||||
char mode_str[32];
|
||||
|
@ -1156,7 +1157,7 @@ static void gauss_smooth_draw_status_header(bContext *C, tGraphSliderOp *gso)
|
|||
ED_workspace_status_text(C, status_str);
|
||||
}
|
||||
|
||||
static void gauss_smooth_modal_update(bContext *C, wmOperator *op)
|
||||
static void gaussian_smooth_modal_update(bContext *C, wmOperator *op)
|
||||
{
|
||||
tGraphSliderOp *gso = op->customdata;
|
||||
|
||||
|
@ -1166,7 +1167,7 @@ static void gauss_smooth_modal_update(bContext *C, wmOperator *op)
|
|||
return;
|
||||
}
|
||||
|
||||
gauss_smooth_draw_status_header(C, gso);
|
||||
gaussian_smooth_draw_status_header(C, gso);
|
||||
|
||||
const float factor = slider_factor_get_and_remember(op);
|
||||
tGaussOperatorData *operator_data = (tGaussOperatorData *)gso->operator_data;
|
||||
|
@ -1189,7 +1190,7 @@ static void gauss_smooth_modal_update(bContext *C, wmOperator *op)
|
|||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
|
||||
}
|
||||
|
||||
static int gauss_smooth_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
static int gaussian_smooth_invoke(bContext *C, wmOperator *op, const wmEvent *event)
|
||||
{
|
||||
const int invoke_result = graph_slider_invoke(C, op, event);
|
||||
|
||||
|
@ -1198,25 +1199,26 @@ static int gauss_smooth_invoke(bContext *C, wmOperator *op, const wmEvent *event
|
|||
}
|
||||
|
||||
tGraphSliderOp *gso = op->customdata;
|
||||
gso->modal_update = gauss_smooth_modal_update;
|
||||
gso->modal_update = gaussian_smooth_modal_update;
|
||||
gso->factor_prop = RNA_struct_find_property(op->ptr, "factor");
|
||||
|
||||
const float sigma = RNA_float_get(op->ptr, "sigma");
|
||||
const int filter_width = RNA_int_get(op->ptr, "filter_width");
|
||||
|
||||
gauss_smooth_allocate_operator_data(gso, filter_width, sigma);
|
||||
gso->free_operator_data = gauss_smooth_free_operator_data;
|
||||
gaussian_smooth_allocate_operator_data(gso, filter_width, sigma);
|
||||
gso->free_operator_data = gaussian_smooth_free_operator_data;
|
||||
|
||||
ED_slider_allow_overshoot_set(gso->slider, false);
|
||||
gauss_smooth_draw_status_header(C, gso);
|
||||
ED_slider_factor_set(gso->slider, 0.0f);
|
||||
gaussian_smooth_draw_status_header(C, gso);
|
||||
|
||||
return invoke_result;
|
||||
}
|
||||
|
||||
static void gauss_smooth_graph_keys(bAnimContext *ac,
|
||||
const float factor,
|
||||
double *kernel,
|
||||
const int filter_width)
|
||||
static void gaussian_smooth_graph_keys(bAnimContext *ac,
|
||||
const float factor,
|
||||
double *kernel,
|
||||
const int filter_width)
|
||||
{
|
||||
ListBase anim_data = {NULL, NULL};
|
||||
ANIM_animdata_filter(ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, ac->datatype);
|
||||
|
@ -1244,7 +1246,7 @@ static void gauss_smooth_graph_keys(bAnimContext *ac,
|
|||
ANIM_animdata_freelist(&anim_data);
|
||||
}
|
||||
|
||||
static int gauss_exec(bContext *C, wmOperator *op)
|
||||
static int gaussian_smooth_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
bAnimContext ac;
|
||||
|
||||
|
@ -1255,9 +1257,9 @@ static int gauss_exec(bContext *C, wmOperator *op)
|
|||
const int filter_width = RNA_int_get(op->ptr, "filter_width");
|
||||
const int kernel_size = filter_width + 1;
|
||||
double *kernel = MEM_callocN(sizeof(double) * kernel_size, "Gauss Kernel");
|
||||
get_1d_gauss_kernel(RNA_float_get(op->ptr, "sigma"), kernel_size, kernel);
|
||||
ED_ANIM_get_1d_gauss_kernel(RNA_float_get(op->ptr, "sigma"), kernel_size, kernel);
|
||||
|
||||
gauss_smooth_graph_keys(&ac, factor, kernel, filter_width);
|
||||
gaussian_smooth_graph_keys(&ac, factor, kernel, filter_width);
|
||||
|
||||
MEM_freeN(kernel);
|
||||
|
||||
|
@ -1267,17 +1269,17 @@ static int gauss_exec(bContext *C, wmOperator *op)
|
|||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
void GRAPH_OT_gauss_smooth(wmOperatorType *ot)
|
||||
void GRAPH_OT_gaussian_smooth(wmOperatorType *ot)
|
||||
{
|
||||
/* Identifiers. */
|
||||
ot->name = "Gauss Smooth";
|
||||
ot->idname = "GRAPH_OT_gauss_smooth";
|
||||
ot->name = "Gaussian Smooth";
|
||||
ot->idname = "GRAPH_OT_gaussian_smooth";
|
||||
ot->description = "Smooth the curve using a Gauss filter";
|
||||
|
||||
/* API callbacks. */
|
||||
ot->invoke = gauss_smooth_invoke;
|
||||
ot->invoke = gaussian_smooth_invoke;
|
||||
ot->modal = graph_slider_modal;
|
||||
ot->exec = gauss_exec;
|
||||
ot->exec = gaussian_smooth_exec;
|
||||
ot->poll = graphop_editable_keyframes_poll;
|
||||
|
||||
/* Flags. */
|
||||
|
|
For non-static functions, I think it might be better to move the
ED_anim_...
prefixes.