WIP: Animation: Smoothing without Shrinkage #106365

Closed
Christoph Lendenfeld wants to merge 6 commits from ChrisLend/blender:curve_smoothing_fft into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
8 changed files with 217 additions and 1 deletions

View File

@ -331,6 +331,7 @@ class GRAPH_MT_slider(Menu):
layout.operator("graph.blend_to_default", text="Blend to Default Value")
layout.operator("graph.ease", text="Ease")
layout.operator("graph.gaussian_smooth", text="Smooth")
layout.operator("graph.curve_straightening")
class GRAPH_MT_view_pie(Menu):

View File

@ -52,6 +52,17 @@ if(WITH_PYTHON)
add_definitions(-DWITH_PYTHON)
endif()
if(WITH_FFTW3)
list(APPEND INC_SYS
${FFTW3_INCLUDE_DIRS}
)
list(APPEND LIB
${FFTW3_LIBRARIES}
)
add_definitions(-DFFTW3=1)
endif()
blender_add_lib(bf_editor_animation "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -37,6 +37,8 @@
#include "ED_keyframes_edit.h"
#include "ED_keyframing.h"
#include "fftw3.h"
/* This file contains code for various keyframe-editing tools which are 'destructive'
* (i.e. they will modify the order of the keyframes, and change the size of the array).
* While some of these tools may eventually be moved out into blenkernel, for now, it is
@ -447,6 +449,126 @@ void smooth_fcurve_segment(FCurve *fcu,
move_key(&fcu->bezt[i], key_y_value);
}
}
void fft_filter_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float C,
const float ku,
const float kl,
const float factor)
{
const float falloff_width = 0.1f;
const float start_frame = fcu->bezt[segment->start_index].vec[1][0];
const float end_frame = fcu->bezt[segment->start_index + segment->length - 1].vec[1][0];
const int sample_count = (int)(end_frame - start_frame) + 1;
float *samples = MEM_callocN(sizeof(float) * sample_count, "FFT Samples");
float *samples_dist = MEM_callocN(sizeof(float) * sample_count, "FFT Samples Dist");
sample_fcurve_segment(fcu, start_frame, samples, sample_count);
float length_curve = 0;
samples_dist[0] = 0.0f;
for (int i = 1; i < sample_count; i++) {
/* Assumes that curve between frames is linear, which is usually approximately right. */
length_curve += sqrt(1 + pow2f(samples[i - 1] - samples[i]));
samples_dist[i] = length_curve;
}
const float sample_distance = 0.5f;
const int eq_dist_sample_count = (int)(length_curve / sample_distance) + 1;
vec2f *eq_dist_samples = MEM_callocN(sizeof(vec2f) * eq_dist_sample_count, "EQ FFT Samples");
float cumulative_distance = 0;
int sample_index = 1;
int eq_dist_index = 0;
while (sample_index < sample_count && eq_dist_index < eq_dist_sample_count) {
const float interp_fac = (cumulative_distance - samples_dist[sample_index - 1]) /
(samples_dist[sample_index] - samples_dist[sample_index - 1]);
const float eval_time = interpf(
start_frame + sample_index, start_frame + sample_index - 1, interp_fac);
eq_dist_samples[eq_dist_index].x = eval_time;
eq_dist_samples[eq_dist_index].y = evaluate_fcurve(fcu, eval_time);
eq_dist_index++;
cumulative_distance += sample_distance;
if (cumulative_distance > samples_dist[sample_index]) {
sample_index++;
}
}
fftw_complex *sample_data = MEM_callocN(sizeof(fftw_complex) * eq_dist_sample_count,
"fftw_sample_data");
for (int i = 0; i < eq_dist_sample_count; i++) {
sample_data[i][0] = (double)eq_dist_samples[i].y;
sample_data[i][1] = 0;
}
fftw_complex *spectrum = MEM_mallocN(sizeof(fftw_complex) * eq_dist_sample_count,
"fftw_spectrum");
fftw_plan fft_plan_forward = fftw_plan_dft_1d(
eq_dist_sample_count, sample_data, spectrum, FFTW_FORWARD, FFTW_ESTIMATE);
fftw_plan fft_plan_backward = fftw_plan_dft_1d(
eq_dist_sample_count, spectrum, sample_data, FFTW_BACKWARD, FFTW_ESTIMATE);
fftw_execute(fft_plan_forward);
fftw_destroy_plan(fft_plan_forward);
double amplitude_max = 0;
for (int i = 0; i < eq_dist_sample_count; i++) {
if (spectrum[i][0] > amplitude_max) {
amplitude_max = spectrum[i][0];
}
}
const double euler = 2.71828182845904523536;
for (int i = 0; i < eq_dist_sample_count; i++) {
const double real = spectrum[i][0];
const double imaginary = spectrum[i][1];
/* Percent of frequency tuples. */
const double frq_percent = (double)(i) / (double)(eq_dist_sample_count - 1);
const double amplitude_percent = fabs(real) / amplitude_max;
/* Before frequency cutoff keep tuples untouched. */
if (amplitude_percent > C) {
continue;
}
/* Will be between 0 and 1. */
// double scale_factor = interpd(0, 1, (frq_percent - C) / falloff_width);
double scale_factor = 0.0f;
scale_factor = scale_factor < 0 ? 0 : scale_factor;
spectrum[i][0] = real * scale_factor;
// spectrum[i][1] = imaginary * scale_factor;
}
fftw_execute(fft_plan_backward);
fftw_destroy_plan(fft_plan_backward);
eq_dist_index = 1;
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float key_x = fcu->bezt[i].vec[1][0];
while (key_x > eq_dist_samples[eq_dist_index].x && eq_dist_index < eq_dist_sample_count) {
eq_dist_index++;
}
if (eq_dist_index >= eq_dist_sample_count) {
break;
}
const double lerp_fac = (eq_dist_samples[eq_dist_index].x - key_x) /
(eq_dist_samples[eq_dist_index].x -
eq_dist_samples[eq_dist_index - 1].x);
const double foo = interpd(
sample_data[eq_dist_index][0], sample_data[eq_dist_index - 1][0], lerp_fac);
fcu->bezt[i].vec[1][1] = (float)foo / eq_dist_sample_count;
}
MEM_SAFE_FREE(spectrum);
MEM_freeN(samples);
MEM_freeN(samples_dist);
MEM_freeN(eq_dist_samples);
MEM_freeN(sample_data);
}
/* ---------------- */
void ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)

View File

@ -437,6 +437,8 @@ void smooth_fcurve_segment(struct FCurve *fcu,
float factor,
int kernel_size,
double *kernel);
void fft_filter_fcurve_segment(
struct FCurve *fcu, struct FCurveSegment *segment, float C, float ku, float kl, float factor);
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);

View File

@ -57,7 +57,6 @@ if(WITH_AUDASPACE)
add_definitions(-DWITH_AUDASPACE)
endif()
blender_add_lib(bf_editor_space_graph "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.h dna_type_offsets.h

View File

@ -117,6 +117,7 @@ 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_gaussian_smooth(struct wmOperatorType *ot);
void GRAPH_OT_curve_straightening(struct wmOperatorType *ot);
void GRAPH_OT_sample(struct wmOperatorType *ot);
void GRAPH_OT_bake(struct wmOperatorType *ot);
void GRAPH_OT_unbake(struct wmOperatorType *ot);

View File

@ -466,6 +466,7 @@ void graphedit_operatortypes(void)
WM_operatortype_append(GRAPH_OT_blend_to_default);
WM_operatortype_append(GRAPH_OT_gaussian_smooth);
WM_operatortype_append(GRAPH_OT_euler_filter);
WM_operatortype_append(GRAPH_OT_curve_straightening);
WM_operatortype_append(GRAPH_OT_delete);
WM_operatortype_append(GRAPH_OT_duplicate);

View File

@ -1315,3 +1315,82 @@ void GRAPH_OT_gaussian_smooth(wmOperatorType *ot)
32);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name FFT Smooth
* \{ */
static void smooth_graph_keys(
bAnimContext *ac, const float factor, const float frq, const float ku, const float kl)
{
ListBase anim_data = {NULL, NULL};
ANIM_animdata_filter(ac, &anim_data, OPERATOR_DATA_FILTER, ac->data, ac->datatype);
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
FCurve *fcu = (FCurve *)ale->key_data;
ListBase segments = find_fcurve_segments(fcu);
LISTBASE_FOREACH (FCurveSegment *, segment, &segments) {
fft_filter_fcurve_segment(fcu, segment, frq, ku, kl, factor);
}
BLI_freelistN(&segments);
ale->update |= ANIM_UPDATE_DEFAULT;
}
ANIM_animdata_update(ac, &anim_data);
ANIM_animdata_freelist(&anim_data);
}
static int curve_straighten_exec(bContext *C, wmOperator *op)
{
bAnimContext ac;
if (ANIM_animdata_get_context(C, &ac) == 0) {
return OPERATOR_CANCELLED;
}
const float factor = RNA_float_get(op->ptr, "factor");
const float frq = RNA_float_get(op->ptr, "cutoff_frequency");
const float falloff_ku = RNA_float_get(op->ptr, "falloff_ku");
const float falloff_kl = RNA_float_get(op->ptr, "falloff_kl");
smooth_graph_keys(&ac, factor, frq, falloff_ku, falloff_kl);
/* Set notifier that keyframes have changed. */
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
return OPERATOR_FINISHED;
}
void GRAPH_OT_curve_straightening(wmOperatorType *ot)
{
/* Identifiers. */
ot->name = "Foo Smooth";
ot->idname = "GRAPH_OT_curve_straightening";
ot->description = "Smooth the curve using a foo filter";
/* API callbacks. */
ot->exec = curve_straighten_exec;
ot->poll = graphop_editable_keyframes_poll;
/* Flags. */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_float_factor(ot->srna,
"factor",
1.0f,
0.0f,
FLT_MAX,
"Factor",
"How much to blend to the default value",
0.0f,
1.0f);
RNA_def_float_factor(
ot->srna, "cutoff_frequency", 0.33f, 0.0f, 1.0f, "Cutoff Frquency", "foo", 0.0f, 1.0f);
RNA_def_float(ot->srna, "falloff_ku", 1.0f, 0, FLT_MAX, "kU", "foo", 1, 32);
RNA_def_float(ot->srna, "falloff_kl", 1.0f, 0, FLT_MAX, "kL", "foo", 1, 32);
}
/** \} */