Files
test/source/blender/editors/animation/keyframes_general.cc
Campbell Barton 155dae94d7 Cleanup: code-comments, use doxygen formatting & spelling corrections
Also move some function doc-strings from the implementation
to their declarations.
2024-03-26 17:55:20 +11:00

2087 lines
69 KiB
C++

/* SPDX-FileCopyrightText: 2008 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edanimation
*/
#include <cfloat>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include "MEM_guardedalloc.h"
#include "BLI_blenlib.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string_utils.hh"
#include "BLI_utildefines.h"
#include "DNA_anim_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_space_types.h"
#include "BKE_action.h"
#include "BKE_curve.hh"
#include "BKE_fcurve.hh"
#include "BKE_main.hh"
#include "BKE_scene.hh"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "RNA_path.hh"
#include "ED_keyframes_edit.hh"
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
/* 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
* fine to have these calls here.
*
* There are also a few tools here which cannot be easily coded for in the other system (yet).
* These may also be moved around at some point, but for now, they are best added here.
*
* - Joshua Leung, Dec 2008
*/
/* **************************************************** */
bool duplicate_fcurve_keys(FCurve *fcu)
{
bool changed = false;
/* this can only work when there is an F-Curve, and also when there are some BezTriples */
if (ELEM(nullptr, fcu, fcu->bezt)) {
return changed;
}
for (int i = 0; i < fcu->totvert; i++) {
/* If a key is selected */
if (fcu->bezt[i].f2 & SELECT) {
/* Expand the list */
BezTriple *newbezt = static_cast<BezTriple *>(
MEM_callocN(sizeof(BezTriple) * (fcu->totvert + 1), "beztriple"));
memcpy(newbezt, fcu->bezt, sizeof(BezTriple) * (i + 1));
memcpy(newbezt + i + 1, fcu->bezt + i, sizeof(BezTriple));
memcpy(newbezt + i + 2, fcu->bezt + i + 1, sizeof(BezTriple) * (fcu->totvert - (i + 1)));
fcu->totvert++;
changed = true;
/* reassign pointers... (free old, and add new) */
MEM_freeN(fcu->bezt);
fcu->bezt = newbezt;
/* Unselect the current key */
BEZT_DESEL_ALL(&fcu->bezt[i]);
i++;
/* Select the copied key */
BEZT_SEL_ALL(&fcu->bezt[i]);
}
}
return changed;
}
/* -------------------------------------------------------------------- */
/** \name Various Tools
* \{ */
void clean_fcurve(bAnimContext *ac,
bAnimListElem *ale,
float thresh,
bool cleardefault,
const bool only_selected_keys)
{
FCurve *fcu = (FCurve *)ale->key_data;
BezTriple *old_bezts, *bezt, *beztn;
BezTriple *lastb;
int totCount, i;
/* Check if any points. */
if ((fcu == nullptr) || (fcu->bezt == nullptr) || (fcu->totvert == 0) ||
(!cleardefault && fcu->totvert == 1))
{
return;
}
/* make a copy of the old BezTriples, and clear F-Curve */
old_bezts = fcu->bezt;
totCount = fcu->totvert;
fcu->bezt = nullptr;
fcu->totvert = 0;
/* now insert first keyframe, as it should be ok */
bezt = old_bezts;
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
if (!(bezt->f2 & SELECT)) {
lastb = fcu->bezt;
lastb->f1 = lastb->f2 = lastb->f3 = 0;
}
/* Loop through BezTriples, comparing them. Skip any that do
* not fit the criteria for "ok" points.
*/
for (i = 1; i < totCount; i++) {
float prev[2], cur[2], next[2];
/* get BezTriples and their values */
if (i < (totCount - 1)) {
beztn = (old_bezts + (i + 1));
next[0] = beztn->vec[1][0];
next[1] = beztn->vec[1][1];
}
else {
beztn = nullptr;
next[0] = next[1] = 0.0f;
}
lastb = (fcu->bezt + (fcu->totvert - 1));
bezt = (old_bezts + i);
/* get references for quicker access */
prev[0] = lastb->vec[1][0];
prev[1] = lastb->vec[1][1];
cur[0] = bezt->vec[1][0];
cur[1] = bezt->vec[1][1];
if (only_selected_keys && !(bezt->f2 & SELECT)) {
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
lastb = (fcu->bezt + (fcu->totvert - 1));
lastb->f1 = lastb->f2 = lastb->f3 = 0;
continue;
}
/* check if current bezt occurs at same time as last ok */
if (IS_EQT(cur[0], prev[0], thresh)) {
/* If there is a next beztriple, and if occurs at the same time, only insert
* if there is a considerable distance between the points, and also if the
* current is further away than the next one is to the previous.
*/
if (beztn && IS_EQT(cur[0], next[0], thresh) && (IS_EQT(next[1], prev[1], thresh) == 0)) {
/* only add if current is further away from previous */
if (cur[1] > next[1]) {
if (IS_EQT(cur[1], prev[1], thresh) == 0) {
/* add new keyframe */
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
}
}
}
else {
/* only add if values are a considerable distance apart */
if (IS_EQT(cur[1], prev[1], thresh) == 0) {
/* add new keyframe */
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
}
}
}
else {
/* checks required are dependent on whether this is last keyframe or not */
if (beztn) {
/* does current have same value as previous and next? */
if (IS_EQT(cur[1], prev[1], thresh) == 0) {
/* add new keyframe */
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
}
else if (IS_EQT(cur[1], next[1], thresh) == 0) {
/* add new keyframe */
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
}
}
else {
/* add if value doesn't equal that of previous */
if (IS_EQT(cur[1], prev[1], thresh) == 0) {
/* add new keyframe */
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
}
}
}
}
/* now free the memory used by the old BezTriples */
if (old_bezts) {
MEM_freeN(old_bezts);
}
/* final step, if there is just one key in fcurve, check if it's
* the default value and if is, remove fcurve completely. */
if (cleardefault && fcu->totvert == 1) {
float default_value = 0.0f;
PointerRNA ptr;
PropertyRNA *prop;
PointerRNA id_ptr = RNA_id_pointer_create(ale->id);
/* get property to read from, and get value as appropriate */
if (RNA_path_resolve_property(&id_ptr, fcu->rna_path, &ptr, &prop)) {
if (RNA_property_type(prop) == PROP_FLOAT) {
default_value = RNA_property_float_get_default_index(&ptr, prop, fcu->array_index);
}
}
if (fcu->bezt->vec[1][1] == default_value) {
BKE_fcurve_delete_keys_all(fcu);
/* check if curve is really unused and if it is, return signal for deletion */
if (BKE_fcurve_is_empty(fcu)) {
AnimData *adt = ale->adt;
blender::animrig::animdata_fcurve_delete(ac, adt, fcu);
ale->key_data = nullptr;
}
}
}
}
/**
* Find the first segment of consecutive selected curve points, starting from \a start_index.
* Keys that have BEZT_FLAG_IGNORE_TAG set are treated as unselected.
* \param r_segment_start_idx: returns the start index of the segment.
* \param r_segment_len: returns the number of curve points in the segment.
* \return whether such a segment was found or not.
*/
static bool find_fcurve_segment(FCurve *fcu,
const int start_index,
int *r_segment_start_idx,
int *r_segment_len)
{
*r_segment_start_idx = 0;
*r_segment_len = 0;
bool in_segment = false;
for (int i = start_index; i < fcu->totvert; i++) {
const bool point_is_selected = fcu->bezt[i].f2 & SELECT;
const bool point_is_ignored = fcu->bezt[i].f2 & BEZT_FLAG_IGNORE_TAG;
if (point_is_selected && !point_is_ignored) {
if (!in_segment) {
*r_segment_start_idx = i;
in_segment = true;
}
(*r_segment_len)++;
}
else if (in_segment) {
/* If the curve point is not selected then we have reached the end of the selected curve
* segment. */
return true; /* Segment found. */
}
}
/* If the last curve point was in the segment, `r_segment_len` and `r_segment_start_idx`
* are already updated and true is returned. */
return in_segment;
}
ListBase find_fcurve_segments(FCurve *fcu)
{
ListBase segments = {nullptr, nullptr};
/* Ignore baked curves. */
if (!fcu->bezt) {
return segments;
}
int segment_start_idx = 0;
int segment_len = 0;
int current_index = 0;
while (find_fcurve_segment(fcu, current_index, &segment_start_idx, &segment_len)) {
FCurveSegment *segment;
segment = static_cast<FCurveSegment *>(MEM_callocN(sizeof(*segment), "FCurveSegment"));
segment->start_index = segment_start_idx;
segment->length = segment_len;
BLI_addtail(&segments, segment);
current_index = segment_start_idx + segment_len;
}
return segments;
}
static const BezTriple *fcurve_segment_start_get(FCurve *fcu, int index)
{
const BezTriple *start_bezt = index - 1 >= 0 ? &fcu->bezt[index - 1] : &fcu->bezt[index];
return start_bezt;
}
static const BezTriple *fcurve_segment_end_get(FCurve *fcu, int index)
{
const BezTriple *end_bezt = index < fcu->totvert ? &fcu->bezt[index] : &fcu->bezt[index - 1];
return end_bezt;
}
/* ---------------- */
void blend_to_neighbor_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *target_bezt;
/* Find which key to blend towards. */
if (factor < 0) {
target_bezt = fcurve_segment_start_get(fcu, segment->start_index);
}
else {
target_bezt = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
}
const float lerp_factor = fabs(factor);
/* Blend each key individually. */
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float key_y_value = interpf(target_bezt->vec[1][1], fcu->bezt[i].vec[1][1], lerp_factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
float get_default_rna_value(FCurve *fcu, PropertyRNA *prop, PointerRNA *ptr)
{
const int len = RNA_property_array_length(ptr, prop);
float default_value = 0;
/* Find the default value of that property. */
switch (RNA_property_type(prop)) {
case PROP_BOOLEAN:
if (len) {
default_value = RNA_property_boolean_get_default_index(ptr, prop, fcu->array_index);
}
else {
default_value = RNA_property_boolean_get_default(ptr, prop);
}
break;
case PROP_INT:
if (len) {
default_value = RNA_property_int_get_default_index(ptr, prop, fcu->array_index);
}
else {
default_value = RNA_property_int_get_default(ptr, prop);
}
break;
case PROP_FLOAT:
if (len) {
default_value = RNA_property_float_get_default_index(ptr, prop, fcu->array_index);
}
else {
default_value = RNA_property_float_get_default(ptr, prop);
}
break;
default:
break;
}
return default_value;
}
void blend_to_default_fcurve(PointerRNA *id_ptr, FCurve *fcu, const float factor)
{
PointerRNA ptr;
PropertyRNA *prop;
/* Check if path is valid. */
if (!RNA_path_resolve_property(id_ptr, fcu->rna_path, &ptr, &prop)) {
return;
}
const float default_value = get_default_rna_value(fcu, prop, &ptr);
/* Blend selected keys to default. */
for (int i = 0; i < fcu->totvert; i++) {
if (!(fcu->bezt[i].f2 & SELECT)) {
continue;
}
const float key_y_value = interpf(default_value, fcu->bezt[i].vec[1][1], factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
void scale_average_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
float y = 0;
/* Find first the average of the y values to then use it in the final calculation. */
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
y += fcu->bezt[i].vec[1][1];
}
const float y_average = y / segment->length;
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float key_y_value = interpf(y_average, fcu->bezt[i].vec[1][1], 1 - factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
struct ButterworthCoefficients {
double *A, *d1, *d2;
int filter_order;
};
ButterworthCoefficients *ED_anim_allocate_butterworth_coefficients(const int filter_order)
{
ButterworthCoefficients *bw_coeff = static_cast<ButterworthCoefficients *>(
MEM_callocN(sizeof(ButterworthCoefficients), "Butterworth Coefficients"));
bw_coeff->filter_order = filter_order;
bw_coeff->d1 = static_cast<double *>(
MEM_callocN(sizeof(double) * filter_order, "coeff filtered"));
bw_coeff->d2 = static_cast<double *>(
MEM_callocN(sizeof(double) * filter_order, "coeff samples"));
bw_coeff->A = static_cast<double *>(MEM_callocN(sizeof(double) * filter_order, "Butterworth A"));
return bw_coeff;
}
void ED_anim_free_butterworth_coefficients(ButterworthCoefficients *bw_coeff)
{
MEM_freeN(bw_coeff->d1);
MEM_freeN(bw_coeff->d2);
MEM_freeN(bw_coeff->A);
MEM_freeN(bw_coeff);
}
void ED_anim_calculate_butterworth_coefficients(const float cutoff_frequency,
const float sampling_frequency,
ButterworthCoefficients *bw_coeff)
{
double s = double(sampling_frequency);
const double a = tan(M_PI * cutoff_frequency / s);
const double a2 = a * a;
double r;
for (int i = 0; i < bw_coeff->filter_order; ++i) {
r = sin(M_PI * (2.0 * i + 1.0) / (4.0 * bw_coeff->filter_order));
s = a2 + 2.0 * a * r + 1.0;
bw_coeff->A[i] = a2 / s;
bw_coeff->d1[i] = 2.0 * (1 - a2) / s;
bw_coeff->d2[i] = -(a2 - 2.0 * a * r + 1.0) / s;
}
}
static double butterworth_filter_value(
double x, double *w0, double *w1, double *w2, ButterworthCoefficients *bw_coeff)
{
for (int i = 0; i < bw_coeff->filter_order; i++) {
w0[i] = bw_coeff->d1[i] * w1[i] + bw_coeff->d2[i] * w2[i] + x;
x = bw_coeff->A[i] * (w0[i] + 2.0 * w1[i] + w2[i]);
w2[i] = w1[i];
w1[i] = w0[i];
}
return x;
}
static float butterworth_calculate_blend_value(float *samples,
float *filtered_values,
const int start_index,
const int end_index,
const int sample_index,
const int blend_in_out)
{
if (start_index == end_index || blend_in_out == 0) {
return samples[start_index];
}
const float blend_in_y_samples = samples[start_index];
const float blend_out_y_samples = samples[end_index];
const float blend_in_y_filtered = filtered_values[start_index + blend_in_out];
const float blend_out_y_filtered = filtered_values[end_index - blend_in_out];
const float slope_in_samples = samples[start_index] - samples[start_index - 1];
const float slope_out_samples = samples[end_index] - samples[end_index + 1];
const float slope_in_filtered = filtered_values[start_index + blend_in_out - 1] -
filtered_values[start_index + blend_in_out];
const float slope_out_filtered = filtered_values[end_index - blend_in_out] -
filtered_values[end_index - blend_in_out - 1];
if (sample_index - start_index <= blend_in_out) {
const int blend_index = sample_index - start_index;
const float blend_in_out_factor = clamp_f(float(blend_index) / blend_in_out, 0.0f, 1.0f);
const float blend_value = interpf(blend_in_y_filtered +
slope_in_filtered * (blend_in_out - blend_index),
blend_in_y_samples + slope_in_samples * blend_index,
blend_in_out_factor);
return blend_value;
}
if (end_index - sample_index <= blend_in_out) {
const int blend_index = end_index - sample_index;
const float blend_in_out_factor = clamp_f(float(blend_index) / blend_in_out, 0.0f, 1.0f);
const float blend_value = interpf(blend_out_y_filtered +
slope_out_filtered * (blend_in_out - blend_index),
blend_out_y_samples + slope_out_samples * blend_index,
blend_in_out_factor);
return blend_value;
}
return 0;
}
void butterworth_smooth_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
float *samples,
const int sample_count,
const float factor,
const int blend_in_out,
const int sample_rate,
ButterworthCoefficients *bw_coeff)
{
const int filter_order = bw_coeff->filter_order;
float *filtered_values = static_cast<float *>(
MEM_callocN(sizeof(float) * sample_count, "Butterworth Filtered FCurve Values"));
double *w0 = static_cast<double *>(MEM_callocN(sizeof(double) * filter_order, "w0"));
double *w1 = static_cast<double *>(MEM_callocN(sizeof(double) * filter_order, "w1"));
double *w2 = static_cast<double *>(MEM_callocN(sizeof(double) * filter_order, "w2"));
/* The values need to be offset so the first sample starts at 0. This avoids oscillations at the
* start and end of the curve. */
const float fwd_offset = samples[0];
for (int i = 0; i < sample_count; i++) {
const double x = double(samples[i] - fwd_offset);
const double filtered_value = butterworth_filter_value(x, w0, w1, w2, bw_coeff);
filtered_values[i] = float(filtered_value) + fwd_offset;
}
for (int i = 0; i < filter_order; i++) {
w0[i] = 0.0;
w1[i] = 0.0;
w2[i] = 0.0;
}
const float bwd_offset = filtered_values[sample_count - 1];
/* Run the filter backwards as well to remove phase offset. */
for (int i = sample_count - 1; i >= 0; i--) {
const double x = double(filtered_values[i] - bwd_offset);
const double filtered_value = butterworth_filter_value(x, w0, w1, w2, bw_coeff);
filtered_values[i] = float(filtered_value) + bwd_offset;
}
const int segment_end_index = segment->start_index + segment->length;
BezTriple left_bezt = fcu->bezt[segment->start_index];
BezTriple right_bezt = fcu->bezt[segment_end_index - 1];
const int samples_start_index = filter_order * sample_rate;
const int samples_end_index = int(right_bezt.vec[1][0] - left_bezt.vec[1][0] + filter_order) *
sample_rate;
const int blend_in_out_clamped = min_ii(blend_in_out,
(samples_end_index - samples_start_index) / 2);
for (int i = segment->start_index; i < segment_end_index; i++) {
float blend_in_out_factor;
if (blend_in_out_clamped == 0) {
blend_in_out_factor = 1;
}
else if (i < segment->start_index + segment->length / 2) {
blend_in_out_factor = min_ff(float(i - segment->start_index) / blend_in_out_clamped, 1.0f);
}
else {
blend_in_out_factor = min_ff(float(segment_end_index - i - 1) / blend_in_out_clamped, 1.0f);
}
const float x_delta = fcu->bezt[i].vec[1][0] - left_bezt.vec[1][0] + filter_order;
/* Using round() instead of casting to int. Casting would introduce a stepping issue when the
* x-value is just below a full frame. */
const int filter_index = round(x_delta * sample_rate);
const float blend_value = butterworth_calculate_blend_value(samples,
filtered_values,
samples_start_index,
samples_end_index,
filter_index,
blend_in_out_clamped);
const float blended_value = interpf(
filtered_values[filter_index], blend_value, blend_in_out_factor);
const float key_y_value = interpf(blended_value, samples[filter_index], factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
MEM_freeN(filtered_values);
MEM_freeN(w0);
MEM_freeN(w1);
MEM_freeN(w2);
}
/* ---------------- */
void ED_ANIM_get_1d_gauss_kernel(const float sigma, const int kernel_size, double *r_kernel)
{
BLI_assert(sigma > 0.0f);
BLI_assert(kernel_size > 0);
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] = 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 < kernel_size; i++) {
r_kernel[i] /= sum;
}
}
void smooth_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
float *samples,
const float factor,
const int kernel_size,
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;
/* Apply the kernel. */
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 float key_y_value = interpf(float(filter_result), samples[sample_index], factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
static float ease_sigmoid_function(const float x, const float width, const float shift)
{
const float x_shift = (x - shift) * width;
const float y = x_shift / sqrt(1 + pow2f(x_shift));
/* Normalize result to 0-1. */
return (y + 1) * 0.5f;
}
void ease_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float factor,
const float width)
{
const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
/* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
* would be a divide by 0. */
if (IS_EQF(key_x_range, 0.0f)) {
return;
}
/* Using the factor on the X-shift we are basically moving the curve horizontally. */
const float shift = -factor;
const float y_min = ease_sigmoid_function(-1, width, shift);
const float y_max = ease_sigmoid_function(1, width, shift);
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
/* Mapping the x-location of the key within the segment to a -1/1 range. */
const float x = ((fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range) * 2 - 1;
const float y = ease_sigmoid_function(x, width, shift);
/* Normalizing the y value to the min and max to ensure that the keys at the end are not
* detached from the rest of the animation. */
const float blend = (y - y_min) * (1 / (y_max - y_min));
const float key_y_value = left_key->vec[1][1] + key_y_range * blend;
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
void blend_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
float y_delta;
if (factor > 0) {
const BezTriple segment_last_key = fcu->bezt[segment->start_index + segment->length - 1];
y_delta = right_key->vec[1][1] - segment_last_key.vec[1][1];
}
else {
const BezTriple segment_first_key = fcu->bezt[segment->start_index];
y_delta = left_key->vec[1][1] - segment_first_key.vec[1][1];
}
const float offset_value = y_delta * fabs(factor);
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float key_y_value = fcu->bezt[i].vec[1][1] + offset_value;
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
static float s_curve(float x, float slope, float width, float height, float xshift, float yshift)
{
/* Formula for 'S' curve we use for the "ease" sliders.
* The shift values move the curve vertically or horizontally.
* The range of the curve used is from 0 to 1 on "x" and "y"
* so we can scale it (width and height) and move it (`xshift` and y `yshift`)
* to crop the part of the curve we need. Slope determines how curvy the shape is. */
float y = height * pow((x - xshift), slope) /
(pow((x - xshift), slope) + pow((width - (x - xshift)), slope)) +
yshift;
/* The curve doesn't do what we want beyond our margins so we clamp the values. */
if (x > xshift + width) {
y = height + yshift;
}
else if (x < xshift) {
y = yshift;
}
return y;
}
void blend_to_ease_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
/* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
* would be a divide by 0. */
if (IS_EQF(key_x_range, 0.0f)) {
return;
}
const float slope = 3.0;
/* By doubling the size of the "S" curve we just one side of it, a "C" shape. */
const float width = 2.0;
const float height = 2.0;
float xy_shift;
/* Shifting the x and y values we can decide what side of the "S" shape to use. */
if (factor > 0) {
xy_shift = -1.0;
}
else {
xy_shift = 0.0;
}
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float x = (fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range;
const float ease = s_curve(x, slope, width, height, xy_shift, xy_shift);
const float base = left_key->vec[1][1] + key_y_range * ease;
float y_delta;
if (factor > 0) {
y_delta = base - fcu->bezt[i].vec[1][1];
}
else {
y_delta = fcu->bezt[i].vec[1][1] - base;
}
const float key_y_value = fcu->bezt[i].vec[1][1] + y_delta * factor;
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
bool match_slope_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
BezTriple beyond_key;
const BezTriple *reference_key;
if (factor >= 0) {
/* Stop the function if there is no key beyond the right neighboring one. */
if (segment->start_index + segment->length >= fcu->totvert - 1) {
return false;
}
reference_key = right_key;
beyond_key = fcu->bezt[segment->start_index + segment->length + 1];
}
else {
/* Stop the function if there is no key beyond the left neighboring one. */
if (segment->start_index <= 1) {
return false;
}
reference_key = left_key;
beyond_key = fcu->bezt[segment->start_index - 2];
}
/* This delta values are used to get the relationship between the bookend keys and the
* reference keys beyond those. */
const float y_delta = beyond_key.vec[1][1] - reference_key->vec[1][1];
const float x_delta = beyond_key.vec[1][0] - reference_key->vec[1][0];
/* Avoids dividing by 0. */
if (x_delta == 0) {
return false;
}
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
/* These new deltas are used to determine the relationship between the current key and the
* bookend ones. */
const float new_x_delta = fcu->bezt[i].vec[1][0] - reference_key->vec[1][0];
const float new_y_delta = new_x_delta * y_delta / x_delta;
const float delta = reference_key->vec[1][1] + new_y_delta - fcu->bezt[i].vec[1][1];
const float key_y_value = fcu->bezt[i].vec[1][1] + delta * fabs(factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
return true;
}
/* ---------------- */
void shear_fcurve_segment(FCurve *fcu,
FCurveSegment *segment,
const float factor,
tShearDirection direction)
{
const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
/* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
* would be a divide by 0. */
if (IS_EQF(key_x_range, 0.0f)) {
return;
}
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
/* For easy calculation of the curve, the values are normalized. */
float normalized_x;
if (direction == SHEAR_FROM_LEFT) {
normalized_x = (fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range;
}
else {
normalized_x = (right_key->vec[1][0] - fcu->bezt[i].vec[1][0]) / key_x_range;
}
const float y_delta = key_y_range * normalized_x;
const float key_y_value = fcu->bezt[i].vec[1][1] + y_delta * factor;
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
void push_pull_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *left_key = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
const float key_x_range = right_key->vec[1][0] - left_key->vec[1][0];
const float key_y_range = right_key->vec[1][1] - left_key->vec[1][1];
/* Happens if there is only 1 key on the FCurve. Needs to be skipped because it
* would be a divide by 0. */
if (IS_EQF(key_x_range, 0.0f)) {
return;
}
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
/* For easy calculation of the curve, the values are normalized. */
const float normalized_x = (fcu->bezt[i].vec[1][0] - left_key->vec[1][0]) / key_x_range;
const float linear = left_key->vec[1][1] + key_y_range * normalized_x;
const float delta = fcu->bezt[i].vec[1][1] - linear;
const float key_y_value = linear + delta * factor;
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
void time_offset_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float frame_offset)
{
/* Two bookend keys of the fcurve are needed to be able to cycle the values. */
const BezTriple *last_key = &fcu->bezt[fcu->totvert - 1];
const BezTriple *first_key = &fcu->bezt[0];
const float fcu_x_range = last_key->vec[1][0] - first_key->vec[1][0];
const float fcu_y_range = last_key->vec[1][1] - first_key->vec[1][1];
const float first_key_x = first_key->vec[1][0];
/* If we operate directly on the fcurve there will be a feedback loop
* so we need to capture the "y" values on an array to then apply them on a second loop. */
float *y_values = static_cast<float *>(
MEM_callocN(sizeof(float) * segment->length, "Time Offset Samples"));
for (int i = 0; i < segment->length; i++) {
/* This simulates the fcu curve moving in time. */
const float time = fcu->bezt[segment->start_index + i].vec[1][0] + frame_offset;
/* Need to normalize time to first_key to specify that as the wrapping point. */
const float wrapped_time = floored_fmod(time - first_key_x, fcu_x_range) + first_key_x;
const float delta_y = fcu_y_range * floorf((time - first_key_x) / fcu_x_range);
const float key_y_value = evaluate_fcurve(fcu, wrapped_time) + delta_y;
y_values[i] = key_y_value;
}
for (int i = 0; i < segment->length; i++) {
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[segment->start_index + i], y_values[i]);
}
MEM_freeN(y_values);
}
/* ---------------- */
void scale_from_fcurve_segment_neighbor(FCurve *fcu,
FCurveSegment *segment,
const float factor,
const FCurveSegmentAnchor anchor)
{
const BezTriple *reference_key;
switch (anchor) {
case FCurveSegmentAnchor::LEFT:
reference_key = fcurve_segment_start_get(fcu, segment->start_index);
break;
case FCurveSegmentAnchor::RIGHT:
reference_key = fcurve_segment_end_get(fcu, segment->start_index + segment->length);
break;
}
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float key_y_value = interpf(fcu->bezt[i].vec[1][1], reference_key->vec[1][1], factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/* ---------------- */
void breakdown_fcurve_segment(FCurve *fcu, FCurveSegment *segment, const float factor)
{
const BezTriple *left_bezt = fcurve_segment_start_get(fcu, segment->start_index);
const BezTriple *right_bezt = fcurve_segment_end_get(fcu,
segment->start_index + segment->length);
const float lerp_factor = (factor + 1) / 2;
for (int i = segment->start_index; i < segment->start_index + segment->length; i++) {
const float key_y_value = interpf(right_bezt->vec[1][1], left_bezt->vec[1][1], lerp_factor);
BKE_fcurve_keyframe_move_value_with_handles(&fcu->bezt[i], key_y_value);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name FCurve Decimate
* \{ */
/* Check if the keyframe interpolation type is supported */
static bool prepare_for_decimate(FCurve *fcu, int i)
{
switch (fcu->bezt[i].ipo) {
case BEZT_IPO_BEZ:
/* We do not need to do anything here as the keyframe already has the required setting.
*/
return true;
case BEZT_IPO_LIN:
/* Convert to a linear bezt curve to be able to use the decimation algorithm. */
fcu->bezt[i].ipo = BEZT_IPO_BEZ;
fcu->bezt[i].h1 = HD_FREE;
fcu->bezt[i].h2 = HD_FREE;
if (i != 0) {
float h1[3];
sub_v3_v3v3(h1, fcu->bezt[i - 1].vec[1], fcu->bezt[i].vec[1]);
mul_v3_fl(h1, 1.0f / 3.0f);
add_v3_v3(h1, fcu->bezt[i].vec[1]);
copy_v3_v3(fcu->bezt[i].vec[0], h1);
}
if (i + 1 != fcu->totvert) {
float h2[3];
sub_v3_v3v3(h2, fcu->bezt[i + 1].vec[1], fcu->bezt[i].vec[1]);
mul_v3_fl(h2, 1.0f / 3.0f);
add_v3_v3(h2, fcu->bezt[i].vec[1]);
copy_v3_v3(fcu->bezt[i].vec[2], h2);
}
return true;
default:
/* These are unsupported. */
return false;
}
}
/* Decimate the given curve segment. */
static void decimate_fcurve_segment(FCurve *fcu,
int bezt_segment_start_idx,
int bezt_segment_len,
float remove_ratio,
float error_sq_max)
{
int selected_len = bezt_segment_len;
/* Make sure that we can remove the start/end point of the segment if they
* are not the start/end point of the curve. BKE_curve_decimate_bezt_array
* has a check that prevents removal of the first and last index in the
* passed array. */
if (bezt_segment_len + bezt_segment_start_idx != fcu->totvert &&
prepare_for_decimate(fcu, bezt_segment_len + bezt_segment_start_idx))
{
bezt_segment_len++;
}
if (bezt_segment_start_idx != 0 && prepare_for_decimate(fcu, bezt_segment_start_idx - 1)) {
bezt_segment_start_idx--;
bezt_segment_len++;
}
const int target_fcurve_verts = ceil(bezt_segment_len - selected_len * remove_ratio);
BKE_curve_decimate_bezt_array(&fcu->bezt[bezt_segment_start_idx],
bezt_segment_len,
12, /* The actual resolution displayed in the viewport is dynamic
* so we just pick a value that preserves the curve shape. */
false,
SELECT,
BEZT_FLAG_TEMP_TAG,
error_sq_max,
target_fcurve_verts);
}
bool decimate_fcurve(bAnimListElem *ale, float remove_ratio, float error_sq_max)
{
FCurve *fcu = (FCurve *)ale->key_data;
/* Check if the curve actually has any points. */
if (fcu == nullptr || fcu->bezt == nullptr || fcu->totvert == 0) {
return true;
}
BezTriple *old_bezts = fcu->bezt;
bool can_decimate_all_selected = true;
for (int i = 0; i < fcu->totvert; i++) {
/* Ignore keyframes that are not supported. */
if (!prepare_for_decimate(fcu, i)) {
can_decimate_all_selected = false;
fcu->bezt[i].f2 |= BEZT_FLAG_IGNORE_TAG;
}
/* Make sure that the temp flag is unset as we use it to determine what to remove. */
fcu->bezt[i].f2 &= ~BEZT_FLAG_TEMP_TAG;
}
ListBase segments = find_fcurve_segments(fcu);
LISTBASE_FOREACH (FCurveSegment *, segment, &segments) {
decimate_fcurve_segment(
fcu, segment->start_index, segment->length, remove_ratio, error_sq_max);
}
BLI_freelistN(&segments);
uint old_totvert = fcu->totvert;
fcu->bezt = nullptr;
fcu->totvert = 0;
for (int i = 0; i < old_totvert; i++) {
BezTriple *bezt = (old_bezts + i);
bezt->f2 &= ~BEZT_FLAG_IGNORE_TAG;
if ((bezt->f2 & BEZT_FLAG_TEMP_TAG) == 0) {
blender::animrig::insert_bezt_fcurve(fcu, bezt, eInsertKeyFlags(0));
}
}
/* now free the memory used by the old BezTriples */
if (old_bezts) {
MEM_freeN(old_bezts);
}
return can_decimate_all_selected;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name FCurve Smooth
* \{ */
/* temp struct used for smooth_fcurve */
struct tSmooth_Bezt {
float *h1, *h2, *h3; /* bezt->vec[0,1,2][1] */
float y1, y2, y3; /* averaged before/new/after y-values */
};
void smooth_fcurve(FCurve *fcu)
{
int totSel = 0;
if (fcu->bezt == nullptr) {
return;
}
/* first loop through - count how many verts are selected */
BezTriple *bezt = fcu->bezt;
for (int i = 0; i < fcu->totvert; i++, bezt++) {
if (BEZT_ISSEL_ANY(bezt)) {
totSel++;
}
}
/* if any points were selected, allocate tSmooth_Bezt points to work on */
if (totSel >= 3) {
tSmooth_Bezt *tarray, *tsb;
/* allocate memory in one go */
tsb = tarray = static_cast<tSmooth_Bezt *>(
MEM_callocN(totSel * sizeof(tSmooth_Bezt), "tSmooth_Bezt Array"));
/* populate tarray with data of selected points */
bezt = fcu->bezt;
for (int i = 0, x = 0; (i < fcu->totvert) && (x < totSel); i++, bezt++) {
if (BEZT_ISSEL_ANY(bezt)) {
/* tsb simply needs pointer to vec, and index */
tsb->h1 = &bezt->vec[0][1];
tsb->h2 = &bezt->vec[1][1];
tsb->h3 = &bezt->vec[2][1];
/* advance to the next tsb to populate */
if (x < totSel - 1) {
tsb++;
}
else {
break;
}
}
}
/* calculate the new smoothed F-Curve's with weighted averages:
* - this is done with two passes to avoid progressive corruption errors
* - uses 5 points for each operation (which stores in the relevant handles)
* - previous: w/a ratio = 3:5:2:1:1
* - next: w/a ratio = 1:1:2:5:3
*/
/* round 1: calculate smoothing deltas and new values */
tsb = tarray;
for (int i = 0; i < totSel; i++, tsb++) {
/* Don't touch end points (otherwise, curves slowly explode,
* as we don't have enough data there). */
if (ELEM(i, 0, (totSel - 1)) == 0) {
const tSmooth_Bezt *tP1 = tsb - 1;
const tSmooth_Bezt *tP2 = (i - 2 > 0) ? (tsb - 2) : (nullptr);
const tSmooth_Bezt *tN1 = tsb + 1;
const tSmooth_Bezt *tN2 = (i + 2 < totSel) ? (tsb + 2) : (nullptr);
const float p1 = *tP1->h2;
const float p2 = (tP2) ? (*tP2->h2) : (*tP1->h2);
const float c1 = *tsb->h2;
const float n1 = *tN1->h2;
const float n2 = (tN2) ? (*tN2->h2) : (*tN1->h2);
/* calculate previous and next, then new position by averaging these */
tsb->y1 = (3 * p2 + 5 * p1 + 2 * c1 + n1 + n2) / 12;
tsb->y3 = (p2 + p1 + 2 * c1 + 5 * n1 + 3 * n2) / 12;
tsb->y2 = (tsb->y1 + tsb->y3) / 2;
}
}
/* round 2: apply new values */
tsb = tarray;
for (int i = 0; i < totSel; i++, tsb++) {
/* don't touch end points, as their values weren't touched above */
if (ELEM(i, 0, (totSel - 1)) == 0) {
/* y2 takes the average of the 2 points */
*tsb->h2 = tsb->y2;
/* handles are weighted between their original values and the averaged values */
*tsb->h1 = ((*tsb->h1) * 0.7f) + (tsb->y1 * 0.3f);
*tsb->h3 = ((*tsb->h3) * 0.7f) + (tsb->y3 * 0.3f);
}
}
/* free memory required for tarray */
MEM_freeN(tarray);
}
/* recalculate handles */
BKE_fcurve_handles_recalc(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
*
* - The copy/paste buffer currently stores a set of temporary F-Curves containing only the
* keyframes that were selected in each of the original F-Curves.
* - All pasted frames are offset by the same amount.
* This is calculated as the difference in the times of the current frame and the
* `first keyframe` (i.e. the earliest one in all channels).
* - The earliest frame is calculated per copy operation.
* \{ */
/* globals for copy/paste data (like for other copy/paste buffers) */
static ListBase animcopybuf = {nullptr, nullptr};
static float animcopy_firstframe = 999999999.0f;
static float animcopy_lastframe = -999999999.0f;
static float animcopy_cfra = 0.0;
/* datatype for use in copy/paste buffer */
struct tAnimCopybufItem {
tAnimCopybufItem *next, *prev;
ID *id; /* ID which owns the curve */
bActionGroup *grp; /* Action Group */
char *rna_path; /* RNA-Path */
int array_index; /* array index */
int totvert; /* number of keyframes stored for this channel */
BezTriple *bezt; /* keyframes in buffer */
short id_type; /* Result of `GS(id->name)`. */
bool is_bone; /* special flag for armature bones */
};
void ANIM_fcurves_copybuf_free()
{
tAnimCopybufItem *aci, *acn;
/* free each buffer element */
for (aci = static_cast<tAnimCopybufItem *>(animcopybuf.first); aci; aci = acn) {
acn = aci->next;
/* free keyframes */
if (aci->bezt) {
MEM_freeN(aci->bezt);
}
/* free RNA-path */
if (aci->rna_path) {
MEM_freeN(aci->rna_path);
}
/* free ourself */
BLI_freelinkN(&animcopybuf, aci);
}
/* restore initial state */
BLI_listbase_clear(&animcopybuf);
animcopy_firstframe = 999999999.0f;
animcopy_lastframe = -999999999.0f;
}
/* ------------------- */
short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
{
Scene *scene = ac->scene;
/* clear buffer first */
ANIM_fcurves_copybuf_free();
/* assume that each of these is an F-Curve */
LISTBASE_FOREACH (bAnimListElem *, ale, anim_data) {
FCurve *fcu = (FCurve *)ale->key_data;
tAnimCopybufItem *aci;
BezTriple *bezt, *nbezt, *newbuf;
int i;
/* firstly, check if F-Curve has any selected keyframes
* - skip if no selected keyframes found (so no need to create unnecessary copy-buffer data)
* - this check should also eliminate any problems associated with using sample-data
*/
if (ANIM_fcurve_keyframes_loop(
nullptr, fcu, nullptr, ANIM_editkeyframes_ok(BEZT_OK_SELECTED), nullptr) == 0)
{
continue;
}
/* init copybuf item info */
aci = static_cast<tAnimCopybufItem *>(
MEM_callocN(sizeof(tAnimCopybufItem), "AnimCopybufItem"));
aci->id = ale->id;
aci->id_type = GS(ale->id->name);
aci->grp = fcu->grp;
aci->rna_path = static_cast<char *>(MEM_dupallocN(fcu->rna_path));
aci->array_index = fcu->array_index;
/* Detect if this is a bone. We do that here rather than during pasting because ID pointers
* will get invalidated if we undo.
* Storing the relevant information here helps avoiding crashes if we undo-repaste. */
if ((aci->id_type == ID_OB) && (((Object *)aci->id)->type == OB_ARMATURE) && aci->rna_path) {
Object *ob = (Object *)aci->id;
bPoseChannel *pchan;
char bone_name[sizeof(pchan->name)];
if (BLI_str_quoted_substr(aci->rna_path, "pose.bones[", bone_name, sizeof(bone_name))) {
pchan = BKE_pose_channel_find_name(ob->pose, bone_name);
if (pchan) {
aci->is_bone = true;
}
}
}
BLI_addtail(&animcopybuf, aci);
/* add selected keyframes to buffer */
/* TODO: currently, we resize array every time we add a new vert -
* this works ok as long as it is assumed only a few keys are copied */
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
if (BEZT_ISSEL_ANY(bezt)) {
/* add to buffer */
newbuf = static_cast<BezTriple *>(
MEM_callocN(sizeof(BezTriple) * (aci->totvert + 1), "copybuf beztriple"));
/* assume that since we are just re-sizing the array, just copy all existing data across */
if (aci->bezt) {
memcpy(newbuf, aci->bezt, sizeof(BezTriple) * (aci->totvert));
}
/* copy current beztriple across too */
nbezt = &newbuf[aci->totvert];
*nbezt = *bezt;
/* ensure copy buffer is selected so pasted keys are selected */
BEZT_SEL_ALL(nbezt);
/* free old array and set the new */
if (aci->bezt) {
MEM_freeN(aci->bezt);
}
aci->bezt = newbuf;
aci->totvert++;
/* check if this is the earliest frame encountered so far */
if (bezt->vec[1][0] < animcopy_firstframe) {
animcopy_firstframe = bezt->vec[1][0];
}
if (bezt->vec[1][0] > animcopy_lastframe) {
animcopy_lastframe = bezt->vec[1][0];
}
}
}
}
/* check if anything ended up in the buffer */
if (ELEM(nullptr, animcopybuf.first, animcopybuf.last)) {
return -1;
}
/* in case 'relative' paste method is used */
animcopy_cfra = scene->r.cfra;
/* everything went fine */
return 0;
}
static void flip_names(tAnimCopybufItem *aci, char **r_name)
{
if (!aci->is_bone) {
return;
}
int ofs_start, ofs_end;
if (!BLI_str_quoted_substr_range(aci->rna_path, "pose.bones[", &ofs_start, &ofs_end)) {
return;
}
char *str_start = aci->rna_path + ofs_start;
const char *str_end = aci->rna_path + ofs_end;
/* Swap out the name.
* NOTE: there is no need to un-escape the string to flip it.
* However the buffer does need to be twice the size. */
char bname_new[MAX_VGROUP_NAME * 2];
char *str_iter;
int len_old, prefix_l, postfix_l;
prefix_l = str_start - aci->rna_path;
len_old = str_end - str_start;
postfix_l = strlen(str_end);
/* Temporary substitute with nullptr terminator. */
BLI_assert(str_start[len_old] == '\"');
str_start[len_old] = 0;
const int len_new = BLI_string_flip_side_name(bname_new, str_start, false, sizeof(bname_new));
str_start[len_old] = '\"';
str_iter = *r_name = static_cast<char *>(
MEM_mallocN(sizeof(char) * (prefix_l + postfix_l + len_new + 1), "flipped_path"));
memcpy(str_iter, aci->rna_path, prefix_l);
str_iter += prefix_l;
memcpy(str_iter, bname_new, len_new);
str_iter += len_new;
memcpy(str_iter, str_end, postfix_l);
str_iter[postfix_l] = '\0';
}
/* ------------------- */
/* most strict method: exact matches only */
static tAnimCopybufItem *pastebuf_match_path_full(FCurve *fcu,
const short from_single,
const short to_simple,
bool flip)
{
tAnimCopybufItem *aci;
for (aci = static_cast<tAnimCopybufItem *>(animcopybuf.first); aci; aci = aci->next) {
if (to_simple || (aci->rna_path && fcu->rna_path)) {
if (!to_simple && flip && aci->is_bone && fcu->rna_path) {
if ((from_single) || (aci->array_index == fcu->array_index)) {
char *name = nullptr;
flip_names(aci, &name);
if (STREQ(name, fcu->rna_path)) {
MEM_freeN(name);
break;
}
MEM_freeN(name);
}
}
else if (to_simple || STREQ(aci->rna_path, fcu->rna_path)) {
if ((from_single) || (aci->array_index == fcu->array_index)) {
break;
}
}
}
}
return aci;
}
/* medium match strictness: path match only (i.e. ignore ID) */
static tAnimCopybufItem *pastebuf_match_path_property(Main *bmain,
FCurve *fcu,
const short from_single,
const short /*to_simple*/)
{
tAnimCopybufItem *aci;
for (aci = static_cast<tAnimCopybufItem *>(animcopybuf.first); aci; aci = aci->next) {
/* check that paths exist */
if (aci->rna_path && fcu->rna_path) {
/* find the property of the fcurve and compare against the end of the tAnimCopybufItem
* more involved since it needs to do path lookups.
* This is not 100% reliable since the user could be editing the curves on a path that won't
* resolve, or a bone could be renamed after copying for eg. but in normal copy & paste
* this should work out ok.
*/
if (BLI_findindex(which_libbase(bmain, aci->id_type), aci->id) == -1) {
/* pedantic but the ID could have been removed, and beats crashing! */
printf("paste_animedit_keys: error ID has been removed!\n");
}
else {
PointerRNA rptr;
PropertyRNA *prop;
PointerRNA id_ptr = RNA_id_pointer_create(aci->id);
if (RNA_path_resolve_property(&id_ptr, aci->rna_path, &rptr, &prop)) {
const char *identifier = RNA_property_identifier(prop);
int len_id = strlen(identifier);
int len_path = strlen(fcu->rna_path);
if (len_id <= len_path) {
/* NOTE: paths which end with "] will fail with this test - Animated ID Props. */
if (STREQ(identifier, fcu->rna_path + (len_path - len_id))) {
if ((from_single) || (aci->array_index == fcu->array_index)) {
break;
}
}
}
}
else {
printf("paste_animedit_keys: failed to resolve path id:%s, '%s'!\n",
aci->id->name,
aci->rna_path);
}
}
}
}
return aci;
}
/* least strict matching heuristic: indices only */
static tAnimCopybufItem *pastebuf_match_index_only(FCurve *fcu,
const short from_single,
const short /*to_simple*/)
{
tAnimCopybufItem *aci;
for (aci = static_cast<tAnimCopybufItem *>(animcopybuf.first); aci; aci = aci->next) {
/* check that paths exist */
if ((from_single) || (aci->array_index == fcu->array_index)) {
break;
}
}
return aci;
}
/* ................ */
static void do_curve_mirror_flippping(tAnimCopybufItem *aci, BezTriple *bezt)
{
if (aci->is_bone) {
const size_t slength = strlen(aci->rna_path);
bool flip = false;
if (BLI_strn_endswith(aci->rna_path, "location", slength) && aci->array_index == 0) {
flip = true;
}
else if (BLI_strn_endswith(aci->rna_path, "rotation_quaternion", slength) &&
ELEM(aci->array_index, 2, 3))
{
flip = true;
}
else if (BLI_strn_endswith(aci->rna_path, "rotation_euler", slength) &&
ELEM(aci->array_index, 1, 2))
{
flip = true;
}
else if (BLI_strn_endswith(aci->rna_path, "rotation_axis_angle", slength) &&
ELEM(aci->array_index, 2, 3))
{
flip = true;
}
if (flip) {
bezt->vec[0][1] = -bezt->vec[0][1];
bezt->vec[1][1] = -bezt->vec[1][1];
bezt->vec[2][1] = -bezt->vec[2][1];
}
}
}
/* helper for paste_animedit_keys() - performs the actual pasting */
static void paste_animedit_keys_fcurve(
FCurve *fcu, tAnimCopybufItem *aci, float offset[2], const eKeyMergeMode merge_mode, bool flip)
{
BezTriple *bezt;
int i;
/* First de-select existing FCurve's keyframes */
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
BEZT_DESEL_ALL(bezt);
}
/* mix mode with existing data */
switch (merge_mode) {
case KEYFRAME_PASTE_MERGE_MIX:
/* do-nothing */
break;
case KEYFRAME_PASTE_MERGE_OVER:
/* remove all keys */
BKE_fcurve_delete_keys_all(fcu);
break;
case KEYFRAME_PASTE_MERGE_OVER_RANGE:
case KEYFRAME_PASTE_MERGE_OVER_RANGE_ALL: {
float f_min;
float f_max;
if (merge_mode == KEYFRAME_PASTE_MERGE_OVER_RANGE) {
f_min = aci->bezt[0].vec[1][0] + offset[0];
f_max = aci->bezt[aci->totvert - 1].vec[1][0] + offset[0];
}
else { /* Entire Range */
f_min = animcopy_firstframe + offset[0];
f_max = animcopy_lastframe + offset[0];
}
/* remove keys in range */
if (f_min < f_max) {
/* select verts in range for removal */
for (i = 0, bezt = fcu->bezt; i < fcu->totvert; i++, bezt++) {
if ((f_min < bezt[0].vec[1][0]) && (bezt[0].vec[1][0] < f_max)) {
bezt->f2 |= SELECT;
}
}
/* remove frames in the range */
BKE_fcurve_delete_keys_selected(fcu);
}
break;
}
}
/* just start pasting, with the first keyframe on the current frame, and so on */
for (i = 0, bezt = aci->bezt; i < aci->totvert; i++, bezt++) {
/* temporarily apply offset to src beztriple while copying */
if (flip) {
do_curve_mirror_flippping(aci, bezt);
}
add_v2_v2(bezt->vec[0], offset);
add_v2_v2(bezt->vec[1], offset);
add_v2_v2(bezt->vec[2], offset);
/* insert the keyframe
* NOTE: we do not want to inherit handles from existing keyframes in this case!
*/
blender::animrig::insert_bezt_fcurve(fcu, bezt, INSERTKEY_OVERWRITE_FULL);
/* un-apply offset from src beztriple after copying */
sub_v2_v2(bezt->vec[0], offset);
sub_v2_v2(bezt->vec[1], offset);
sub_v2_v2(bezt->vec[2], offset);
if (flip) {
do_curve_mirror_flippping(aci, bezt);
}
}
/* recalculate F-Curve's handles? */
BKE_fcurve_handles_recalc(fcu);
}
const EnumPropertyItem rna_enum_keyframe_paste_offset_items[] = {
{KEYFRAME_PASTE_OFFSET_CFRA_START,
"START",
0,
"Frame Start",
"Paste keys starting at current frame"},
{KEYFRAME_PASTE_OFFSET_CFRA_END, "END", 0, "Frame End", "Paste keys ending at current frame"},
{KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE,
"RELATIVE",
0,
"Frame Relative",
"Paste keys relative to the current frame when copying"},
{KEYFRAME_PASTE_OFFSET_NONE, "NONE", 0, "No Offset", "Paste keys from original time"},
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_keyframe_paste_offset_value_items[] = {
{KEYFRAME_PASTE_VALUE_OFFSET_LEFT_KEY,
"LEFT_KEY",
0,
"Left Key",
"Paste keys with the first key matching the key left of the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_RIGHT_KEY,
"RIGHT_KEY",
0,
"Right Key",
"Paste keys with the last key matching the key right of the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_CFRA,
"CURRENT_FRAME",
0,
"Current Frame Value",
"Paste keys relative to the value of the curve under the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_CURSOR,
"CURSOR_VALUE",
0,
"Cursor Value",
"Paste keys relative to the Y-Position of the cursor"},
{KEYFRAME_PASTE_VALUE_OFFSET_NONE,
"NONE",
0,
"No Offset",
"Paste keys with the same value as they were copied"},
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_keyframe_paste_merge_items[] = {
{KEYFRAME_PASTE_MERGE_MIX, "MIX", 0, "Mix", "Overlay existing with new keys"},
{KEYFRAME_PASTE_MERGE_OVER, "OVER_ALL", 0, "Overwrite All", "Replace all keys"},
{KEYFRAME_PASTE_MERGE_OVER_RANGE,
"OVER_RANGE",
0,
"Overwrite Range",
"Overwrite keys in pasted range"},
{KEYFRAME_PASTE_MERGE_OVER_RANGE_ALL,
"OVER_RANGE_ALL",
0,
"Overwrite Entire Range",
"Overwrite keys in pasted range, using the range of all copied keys"},
{0, nullptr, 0, nullptr, nullptr},
};
static float paste_get_y_offset(bAnimContext *ac,
tAnimCopybufItem *aci,
bAnimListElem *ale,
const eKeyPasteValueOffset value_offset_mode)
{
FCurve *fcu = (FCurve *)ale->data;
const float cfra = BKE_scene_frame_get(ac->scene);
switch (value_offset_mode) {
case KEYFRAME_PASTE_VALUE_OFFSET_CURSOR: {
SpaceGraph *sipo = (SpaceGraph *)ac->sl;
const float offset = sipo->cursorVal - aci->bezt[0].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_CFRA: {
const float cfra_y = evaluate_fcurve(fcu, cfra);
const float offset = cfra_y - aci->bezt[0].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_LEFT_KEY: {
bool replace;
const int fcu_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, cfra, fcu->totvert, &replace);
BezTriple left_key = fcu->bezt[max_ii(fcu_index - 1, 0)];
const float offset = left_key.vec[1][1] - aci->bezt[0].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_RIGHT_KEY: {
bool replace;
const int fcu_index = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, cfra, fcu->totvert, &replace);
BezTriple right_key = fcu->bezt[min_ii(fcu_index, fcu->totvert - 1)];
const float offset = right_key.vec[1][1] - aci->bezt[aci->totvert - 1].vec[1][1];
return offset;
}
case KEYFRAME_PASTE_VALUE_OFFSET_NONE:
break;
}
return 0.0f;
}
eKeyPasteError paste_animedit_keys(bAnimContext *ac,
ListBase *anim_data,
const eKeyPasteOffset offset_mode,
const eKeyPasteValueOffset value_offset_mode,
const eKeyMergeMode merge_mode,
bool flip)
{
bAnimListElem *ale;
const Scene *scene = (ac->scene);
const bool from_single = BLI_listbase_is_single(&animcopybuf);
const bool to_simple = BLI_listbase_is_single(anim_data);
float offset[2];
int pass;
/* check if buffer is empty */
if (BLI_listbase_is_empty(&animcopybuf)) {
return KEYFRAME_PASTE_NOTHING_TO_PASTE;
}
if (BLI_listbase_is_empty(anim_data)) {
return KEYFRAME_PASTE_NOWHERE_TO_PASTE;
}
/* methods of offset */
switch (offset_mode) {
case KEYFRAME_PASTE_OFFSET_CFRA_START:
offset[0] = float(scene->r.cfra - animcopy_firstframe);
break;
case KEYFRAME_PASTE_OFFSET_CFRA_END:
offset[0] = float(scene->r.cfra - animcopy_lastframe);
break;
case KEYFRAME_PASTE_OFFSET_CFRA_RELATIVE:
offset[0] = float(scene->r.cfra - animcopy_cfra);
break;
case KEYFRAME_PASTE_OFFSET_NONE:
offset[0] = 0.0f;
break;
}
if (from_single && to_simple) {
/* 1:1 match, no tricky checking, just paste */
FCurve *fcu;
tAnimCopybufItem *aci;
ale = static_cast<bAnimListElem *>(anim_data->first);
fcu = (FCurve *)ale->data; /* destination F-Curve */
aci = static_cast<tAnimCopybufItem *>(animcopybuf.first);
offset[1] = paste_get_y_offset(ac, aci, ale, value_offset_mode);
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, false);
ale->update |= ANIM_UPDATE_DEFAULT;
}
else {
/* from selected channels
* This "passes" system aims to try to find "matching" channels to paste keyframes
* into with increasingly loose matching heuristics. The process finishes when at least
* one F-Curve has been pasted into.
*/
for (pass = 0; pass < 3; pass++) {
uint totmatch = 0;
LISTBASE_FOREACH (bAnimListElem *, ale, anim_data) {
/* Find buffer item to paste from:
* - If names don't matter (i.e. only 1 channel in buffer), don't check id/group
* - If names do matter, only check if id-type is ok for now
* (group check is not that important).
* - Most importantly, rna-paths should match (array indices are unimportant for now)
*/
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
FCurve *fcu = (FCurve *)ale->data; /* destination F-Curve */
tAnimCopybufItem *aci = nullptr;
switch (pass) {
case 0:
/* most strict, must be exact path match data_path & index */
aci = pastebuf_match_path_full(fcu, from_single, to_simple, flip);
break;
case 1:
/* less strict, just compare property names */
aci = pastebuf_match_path_property(ac->bmain, fcu, from_single, to_simple);
break;
case 2:
/* Comparing properties gave no results, so just do index comparisons */
aci = pastebuf_match_index_only(fcu, from_single, to_simple);
break;
}
/* copy the relevant data from the matching buffer curve */
if (aci) {
totmatch++;
offset[1] = paste_get_y_offset(ac, aci, ale, value_offset_mode);
if (adt) {
ANIM_nla_mapping_apply_fcurve(adt, static_cast<FCurve *>(ale->key_data), false, false);
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, flip);
ANIM_nla_mapping_apply_fcurve(adt, static_cast<FCurve *>(ale->key_data), true, false);
}
else {
paste_animedit_keys_fcurve(fcu, aci, offset, merge_mode, flip);
}
}
ale->update |= ANIM_UPDATE_DEFAULT;
}
/* don't continue if some fcurves were pasted */
if (totmatch) {
break;
}
}
}
ANIM_animdata_update(ac, anim_data);
return KEYFRAME_PASTE_OK;
}
/** \} */