Refactor: move FCurve baking code to animrig #120984

Merged
Christoph Lendenfeld merged 4 commits from ChrisLend/blender:refactor_move_fcurve_bake into main 2024-04-30 12:22:57 +02:00
9 changed files with 242 additions and 233 deletions

View File

@ -89,4 +89,31 @@ int insert_vert_fcurve(FCurve *fcu,
const KeyframeSettings &settings,
eInsertKeyFlags flag);
/**
* \param sample_rate: indicates how many samples per frame should be generated.
* \param r_samples: Is expected to be an array large enough to hold `sample_count`.
*/
void sample_fcurve_segment(

r_samples has another name (samples) in the function definition. These should be consistent. I think samples is actually better. If it were to be named r_samples, I'd expect it to receive a single float (as that would be the typical use for a float * return parameter).

Probably also good to document that samples is expected to be an array that's large enough to hold sample_count values.

`r_samples` has another name (`samples`) in the function definition. These should be consistent. I think `samples` is actually better. If it were to be named `r_samples`, I'd expect it to receive a single float (as that would be the typical use for a `float *` return parameter). Probably also good to document that `samples` is expected to be an array that's large enough to hold `sample_count` values.
FCurve *fcu, float start_frame, float sample_rate, float *samples, int sample_count);
enum class BakeCurveRemove {
REMOVE_NONE = 0,
REMOVE_IN_RANGE = 1,
REMOVE_OUT_RANGE = 2,
REMOVE_ALL = 3,
};
/**
* Creates keyframes in the given range at the given step interval.
* \param range: start and end frame to bake. Is inclusive on both ends.
* \param remove_existing: choice which keys to remove in relation to the given range.
*/
void bake_fcurve(FCurve *fcu, blender::int2 range, float step, BakeCurveRemove remove_existing);
/**
* Fill the space between selected keyframes with keyframes on full frames.
* E.g. With a key selected on frame 1 and 3 it will insert a key on frame 2.
*/
void bake_fcurve_segments(FCurve *fcu);
} // namespace blender::animrig

View File

@ -326,4 +326,195 @@ int insert_vert_fcurve(FCurve *fcu,
return a;
}
void sample_fcurve_segment(FCurve *fcu,
const float start_frame,
const float sample_rate,
float *samples,
const int sample_count)
{
for (int i = 0; i < sample_count; i++) {
const float evaluation_time = start_frame + (float(i) / sample_rate);
samples[i] = evaluate_fcurve(fcu, evaluation_time);
}
}
static void remove_fcurve_key_range(FCurve *fcu,
const int2 range,
const BakeCurveRemove removal_mode)
{
switch (removal_mode) {
case BakeCurveRemove::REMOVE_ALL: {
BKE_fcurve_delete_keys_all(fcu);
break;
}
case BakeCurveRemove::REMOVE_OUT_RANGE: {
bool replace;
int before_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[0], fcu->totvert, &replace);
if (before_index > 0) {
BKE_fcurve_delete_keys(fcu, {0, uint(before_index)});
}
int after_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[1], fcu->totvert, &replace);
/* #REMOVE_OUT_RANGE is treated as exclusive on both ends. */
if (replace) {
after_index++;
}
if (after_index < fcu->totvert) {
BKE_fcurve_delete_keys(fcu, {uint(after_index), fcu->totvert});
}
break;
}
case BakeCurveRemove::REMOVE_IN_RANGE: {
bool replace;
const int range_start_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[0], fcu->totvert, &replace);
int range_end_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[1], fcu->totvert, &replace);
if (replace) {
range_end_index++;
}
if (range_end_index > range_start_index) {
BKE_fcurve_delete_keys(fcu, {uint(range_start_index), uint(range_end_index)});
}
break;
}
default:
break;
}
}
void bake_fcurve(FCurve *fcu,
const int2 range,
const float step,
const BakeCurveRemove remove_existing)
{
BLI_assert(step > 0);
const int sample_count = (range[1] - range[0]) / step + 1;
float *samples = static_cast<float *>(
MEM_callocN(sample_count * sizeof(float), "Channel Bake Samples"));
const float sample_rate = 1.0f / step;
sample_fcurve_segment(fcu, range[0], sample_rate, samples, sample_count);
if (remove_existing != BakeCurveRemove::REMOVE_NONE) {
remove_fcurve_key_range(fcu, range, remove_existing);
}
BezTriple *baked_keys = static_cast<BezTriple *>(
MEM_callocN(sample_count * sizeof(BezTriple), "beztriple"));
const KeyframeSettings settings = get_keyframe_settings(true);
for (int i = 0; i < sample_count; i++) {
BezTriple *key = &baked_keys[i];
float2 key_position = {range[0] + i * step, samples[i]};
initialize_bezt(key, key_position, settings, eFCurve_Flags(fcu->flag));
}
int merged_size;
BezTriple *merged_bezt = BKE_bezier_array_merge(
baked_keys, sample_count, fcu->bezt, fcu->totvert, &merged_size);
if (fcu->bezt != nullptr) {
/* Can happen if we removed all keys beforehand. */
MEM_freeN(fcu->bezt);
}
MEM_freeN(baked_keys);
fcu->bezt = merged_bezt;
fcu->totvert = merged_size;
MEM_freeN(samples);
BKE_fcurve_handles_recalc(fcu);
}
struct TempFrameValCache {
float frame, val;
};
void bake_fcurve_segments(FCurve *fcu)
{
BezTriple *bezt, *start = nullptr, *end = nullptr;
TempFrameValCache *value_cache, *fp;
int sfra, range;
int i, n;
if (fcu->bezt == nullptr) {
return;
}
KeyframeSettings settings = get_keyframe_settings(true);
settings.keyframe_type = BEZT_KEYTYPE_BREAKDOWN;
/* Find selected keyframes... once pair has been found, add keyframes. */
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
/* check if selected, and which end this is */
if (BEZT_ISSEL_ANY(bezt)) {
if (start) {
/* If next bezt is also selected, don't start sampling yet,
* but instead wait for that one to reconsider, to avoid
* changing the curve when sampling consecutive segments
* (#53229)
*/
if (i < fcu->totvert - 1) {
BezTriple *next = &fcu->bezt[i + 1];
if (BEZT_ISSEL_ANY(next)) {
continue;
}
}
end = bezt;
/* Cache values then add keyframes using these values, as adding
* keyframes while sampling will affect the outcome...
* - Only start sampling+adding from index=1, so that we don't overwrite original keyframe.
*/
range = int(ceil(end->vec[1][0] - start->vec[1][0]));
sfra = int(floor(start->vec[1][0]));
if (range) {
value_cache = static_cast<TempFrameValCache *>(
MEM_callocN(sizeof(TempFrameValCache) * range, "IcuFrameValCache"));
/* Sample values. */
for (n = 1, fp = value_cache; n < range && fp; n++, fp++) {
fp->frame = float(sfra + n);
fp->val = evaluate_fcurve(fcu, fp->frame);
}
/* Add keyframes with these, tagging as 'breakdowns'. */
for (n = 1, fp = value_cache; n < range && fp; n++, fp++) {
blender::animrig::insert_vert_fcurve(
fcu, {fp->frame, fp->val}, settings, eInsertKeyFlags(1));
}
MEM_freeN(value_cache);
/* As we added keyframes, we need to compensate so that bezt is at the right place. */
bezt = fcu->bezt + i + range - 1;
i += (range - 1);
}
/* The current selection island has ended, so start again from scratch. */
start = nullptr;
end = nullptr;
}
else {
/* Just set start keyframe. */
start = bezt;
end = nullptr;
}
}
}
BKE_fcurve_handles_recalc(fcu);
}
} // namespace blender::animrig

View File

@ -57,6 +57,7 @@
#include "ED_select_utils.hh"
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
#include "WM_api.hh"
#include "WM_types.hh"
@ -4333,6 +4334,7 @@ static const EnumPropertyItem channel_bake_key_options[] = {
static int channels_bake_exec(bContext *C, wmOperator *op)
{
using namespace blender::animrig;
bAnimContext ac;
/* Get editor data. */

View File

@ -1223,209 +1223,6 @@ void smooth_fcurve(FCurve *fcu)
/** \} */
/* -------------------------------------------------------------------- */
/** \name FCurve Sample
* \{ */
/* little cache for values... */
struct TempFrameValCache {
float frame, val;
};
void sample_fcurve_segment(FCurve *fcu,
const float start_frame,
const float sample_rate,
float *samples,
const int sample_count)
{
for (int i = 0; i < sample_count; i++) {
const float evaluation_time = start_frame + (float(i) / sample_rate);
samples[i] = evaluate_fcurve(fcu, evaluation_time);
}
}
static void remove_fcurve_key_range(FCurve *fcu,
const blender::int2 range,
const BakeCurveRemove removal_mode)
{
switch (removal_mode) {
case BakeCurveRemove::REMOVE_ALL: {
BKE_fcurve_delete_keys_all(fcu);
break;
}
case BakeCurveRemove::REMOVE_OUT_RANGE: {
bool replace;
int before_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[0], fcu->totvert, &replace);
if (before_index > 0) {
BKE_fcurve_delete_keys(fcu, {0, uint(before_index)});
}
int after_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[1], fcu->totvert, &replace);
/* #REMOVE_OUT_RANGE is treated as exclusive on both ends. */
if (replace) {
after_index++;
}
if (after_index < fcu->totvert) {
BKE_fcurve_delete_keys(fcu, {uint(after_index), fcu->totvert});
}
break;
}
case BakeCurveRemove::REMOVE_IN_RANGE: {
bool replace;
const int range_start_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[0], fcu->totvert, &replace);
int range_end_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, range[1], fcu->totvert, &replace);
if (replace) {
range_end_index++;
}
if (range_end_index > range_start_index) {
BKE_fcurve_delete_keys(fcu, {uint(range_start_index), uint(range_end_index)});
}
break;
}
default:
break;
}
}
void bake_fcurve(FCurve *fcu,
const blender::int2 range,
const float step,
const BakeCurveRemove remove_existing)
{
using namespace blender::animrig;
BLI_assert(step > 0);
const int sample_count = (range[1] - range[0]) / step + 1;
float *samples = static_cast<float *>(
MEM_callocN(sample_count * sizeof(float), "Channel Bake Samples"));
const float sample_rate = 1.0f / step;
sample_fcurve_segment(fcu, range[0], sample_rate, samples, sample_count);
if (remove_existing != BakeCurveRemove::REMOVE_NONE) {
remove_fcurve_key_range(fcu, range, remove_existing);
}
BezTriple *baked_keys = static_cast<BezTriple *>(
MEM_callocN(sample_count * sizeof(BezTriple), "beztriple"));
const KeyframeSettings settings = get_keyframe_settings(true);
for (int i = 0; i < sample_count; i++) {
BezTriple *key = &baked_keys[i];
blender::float2 key_position = {range[0] + i * step, samples[i]};
initialize_bezt(key, key_position, settings, eFCurve_Flags(fcu->flag));
}
int merged_size;
BezTriple *merged_bezt = BKE_bezier_array_merge(
baked_keys, sample_count, fcu->bezt, fcu->totvert, &merged_size);
if (fcu->bezt != nullptr) {
/* Can happen if we removed all keys beforehand. */
MEM_freeN(fcu->bezt);
}
MEM_freeN(baked_keys);
fcu->bezt = merged_bezt;
fcu->totvert = merged_size;
MEM_freeN(samples);
BKE_fcurve_handles_recalc(fcu);
}
void bake_fcurve_segments(FCurve *fcu)
{
using namespace blender::animrig;
BezTriple *bezt, *start = nullptr, *end = nullptr;
TempFrameValCache *value_cache, *fp;
int sfra, range;
int i, n;
if (fcu->bezt == nullptr) { /* ignore baked */
return;
}
KeyframeSettings settings = get_keyframe_settings(true);
settings.keyframe_type = BEZT_KEYTYPE_BREAKDOWN;
/* Find selected keyframes... once pair has been found, add keyframes. */
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
/* check if selected, and which end this is */
if (BEZT_ISSEL_ANY(bezt)) {
if (start) {
/* If next bezt is also selected, don't start sampling yet,
* but instead wait for that one to reconsider, to avoid
* changing the curve when sampling consecutive segments
* (#53229)
*/
if (i < fcu->totvert - 1) {
BezTriple *next = &fcu->bezt[i + 1];
if (BEZT_ISSEL_ANY(next)) {
continue;
}
}
/* set end */
end = bezt;
/* cache values then add keyframes using these values, as adding
* keyframes while sampling will affect the outcome...
* - only start sampling+adding from index=1, so that we don't overwrite original keyframe
*/
range = int(ceil(end->vec[1][0] - start->vec[1][0]));
sfra = int(floor(start->vec[1][0]));
if (range) {
value_cache = static_cast<TempFrameValCache *>(
MEM_callocN(sizeof(TempFrameValCache) * range, "IcuFrameValCache"));
/* sample values */
for (n = 1, fp = value_cache; n < range && fp; n++, fp++) {
fp->frame = float(sfra + n);
fp->val = evaluate_fcurve(fcu, fp->frame);
}
/* add keyframes with these, tagging as 'breakdowns' */
for (n = 1, fp = value_cache; n < range && fp; n++, fp++) {
blender::animrig::insert_vert_fcurve(
fcu, {fp->frame, fp->val}, settings, eInsertKeyFlags(1));
}
/* free temp cache */
MEM_freeN(value_cache);
/* as we added keyframes, we need to compensate so that bezt is at the right place */
bezt = fcu->bezt + i + range - 1;
i += (range - 1);
}
/* the current selection island has ended, so start again from scratch */
start = nullptr;
end = nullptr;
}
else {
/* just set start keyframe */
start = bezt;
end = nullptr;
}
}
}
/* recalculate channel's handles? */
BKE_fcurve_handles_recalc(fcu);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Copy/Paste Tools
*

View File

@ -511,24 +511,6 @@ void blend_to_default_fcurve(PointerRNA *id_ptr, FCurve *fcu, float factor);
* Use a weighted moving-means method to reduce intensity of fluctuations.
*/
void smooth_fcurve(FCurve *fcu);
void bake_fcurve_segments(FCurve *fcu);
/**
* \param sample_rate: indicates how many samples per frame should be generated.
*/
void sample_fcurve_segment(
FCurve *fcu, float start_frame, float sample_rate, float *r_samples, int sample_count);
enum class BakeCurveRemove {
REMOVE_NONE = 0,
REMOVE_IN_RANGE = 1,
REMOVE_OUT_RANGE = 2,
REMOVE_ALL = 3,
};
/** Creates keyframes in the given range at the given step interval.
* \param range: start and end frame to bake. Is inclusive on both ends.
* \param remove_existing: choice which keys to remove in relation to the given range.
*/
void bake_fcurve(FCurve *fcu, blender::int2 range, float step, BakeCurveRemove remove_existing);
/* ----------- */

View File

@ -1300,7 +1300,7 @@ static void bake_action_keys(bAnimContext *ac)
/* Loop through filtered data and add keys between selected keyframes on every frame. */
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
bake_fcurve_segments((FCurve *)ale->key_data);
blender::animrig::bake_fcurve_segments((FCurve *)ale->key_data);
ale->update |= ANIM_UPDATE_DEPS;
}

View File

@ -1337,7 +1337,7 @@ static void bake_graph_keys(bAnimContext *ac)
/* Loop through filtered data and add keys between selected keyframes on every frame. */
LISTBASE_FOREACH (bAnimListElem *, ale, &anim_data) {
bake_fcurve_segments((FCurve *)ale->key_data);
blender::animrig::bake_fcurve_segments((FCurve *)ale->key_data);
ale->update |= ANIM_UPDATE_DEPS;
}

View File

@ -42,6 +42,8 @@
#include "WM_api.hh"
#include "WM_types.hh"
#include "ANIM_fcurve.hh"
#include "graph_intern.hh"
/* -------------------------------------------------------------------- */
@ -1836,7 +1838,8 @@ static void gaussian_smooth_allocate_operator_data(tGraphSliderOp *gso,
(filter_width * 2 + 1);
float *samples = static_cast<float *>(
MEM_callocN(sizeof(float) * sample_count, "Smooth FCurve Op Samples"));
sample_fcurve_segment(fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
segment_link->samples = samples;
BLI_addtail(&segment_links, segment_link);
}
@ -1938,7 +1941,8 @@ static void gaussian_smooth_graph_keys(bAnimContext *ac,
(filter_width * 2 + 1);
float *samples = static_cast<float *>(
MEM_callocN(sizeof(float) * sample_count, "Smooth FCurve Op Samples"));
sample_fcurve_segment(fcu, left_bezt.vec[1][0] - filter_width, 1, samples, sample_count);
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);
MEM_freeN(samples);
}
@ -2077,7 +2081,7 @@ static void btw_smooth_allocate_operator_data(tGraphSliderOp *gso,
&right_bezt, &left_bezt, filter_order, samples_per_frame);
float *samples = static_cast<float *>(
MEM_callocN(sizeof(float) * sample_count, "Btw Smooth FCurve Op Samples"));
sample_fcurve_segment(
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_order, samples_per_frame, samples, sample_count);
segment_link->samples = samples;
segment_link->sample_count = sample_count;
@ -2206,7 +2210,7 @@ static void btw_smooth_graph_keys(bAnimContext *ac,
&right_bezt, &left_bezt, filter_order, samples_per_frame);
float *samples = static_cast<float *>(
MEM_callocN(sizeof(float) * sample_count, "Smooth FCurve Op Samples"));
sample_fcurve_segment(
blender::animrig::sample_fcurve_segment(
fcu, left_bezt.vec[1][0] - filter_order, samples_per_frame, samples, sample_count);
butterworth_smooth_fcurve_segment(
fcu, segment, samples, sample_count, factor, blend_in_out, samples_per_frame, bw_coeff);

View File

@ -17,7 +17,8 @@
#include "DNA_anim_types.h"
#include "DNA_scene_types.h"
#include "ED_keyframes_edit.hh"
#include "ANIM_fcurve.hh"
#include "rna_internal.hh" /* own include */
@ -72,6 +73,7 @@ static void rna_FCurve_bake(FCurve *fcu,
float step,
int remove_existing_as_int)
{
using namespace blender::animrig;
if (start_frame >= end_frame) {
BKE_reportf(reports,
RPT_ERROR,
@ -89,18 +91,22 @@ static void rna_FCurve_bake(FCurve *fcu,
#else
static const EnumPropertyItem channel_bake_remove_options[] = {
{int(BakeCurveRemove::REMOVE_NONE), "NONE", 0, "None", "Keep all keys"},
{int(BakeCurveRemove::REMOVE_IN_RANGE),
{int(blender::animrig::BakeCurveRemove::REMOVE_NONE), "NONE", 0, "None", "Keep all keys"},
{int(blender::animrig::BakeCurveRemove::REMOVE_IN_RANGE),
"IN_RANGE",
0,
"In Range",
"Remove all keys within the defined range"},
{int(BakeCurveRemove::REMOVE_OUT_RANGE),
{int(blender::animrig::BakeCurveRemove::REMOVE_OUT_RANGE),
"OUT_RANGE",
0,
"Outside Range",
"Remove all keys outside the defined range"},
{int(BakeCurveRemove::REMOVE_ALL), "ALL", 0, "All", "Remove all existing keys"},
{int(blender::animrig::BakeCurveRemove::REMOVE_ALL),
"ALL",
0,
"All",
"Remove all existing keys"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -160,7 +166,7 @@ void RNA_api_fcurves(StructRNA *srna)
RNA_def_enum(func,
"remove",
channel_bake_remove_options,
int(BakeCurveRemove::REMOVE_IN_RANGE),
int(blender::animrig::BakeCurveRemove::REMOVE_IN_RANGE),
"Remove Options",
"Choose which keys should be automatically removed by the bake");
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_PYFUNC_OPTIONAL);