Files
Brecht Van Lommel 637c6497e9 Refactor: Use more typed MEM_calloc<>, avoid unnecessary size_t cast
Handle some cases that were missed in previous refactor. And eliminate
unnecessary size_t casts as these could hide issues.

Pull Request: https://projects.blender.org/blender/blender/pulls/137404
2025-04-21 17:59:41 +02:00

719 lines
22 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup animrig
*/
#include <cfloat>
#include <cmath>
#include <cstring>
#include "ANIM_animdata.hh"
#include "ANIM_fcurve.hh"
#include "BKE_fcurve.hh"
#include "BLI_math_base.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string.h"
#include "DNA_anim_types.h"
#include "MEM_guardedalloc.h"
namespace blender::animrig {
KeyframeSettings get_keyframe_settings(const bool from_userprefs)
{
KeyframeSettings settings = {};
settings.keyframe_type = BEZT_KEYTYPE_KEYFRAME;
settings.handle = HD_AUTO_ANIM;
settings.interpolation = BEZT_IPO_BEZ;
if (from_userprefs) {
settings.interpolation = eBezTriple_Interpolation(U.ipo_new);
settings.handle = eBezTriple_Handle(U.keyhandles_new);
}
return settings;
}
const FCurve *fcurve_find(Span<const FCurve *> fcurves, const FCurveDescriptor &fcurve_descriptor)
{
for (const FCurve *fcurve : fcurves) {
/* Check indices first, much cheaper than a string comparison. */
if (fcurve->array_index == fcurve_descriptor.array_index && fcurve->rna_path &&
StringRef(fcurve->rna_path) == fcurve_descriptor.rna_path)
{
return fcurve;
}
}
return nullptr;
}
FCurve *fcurve_find(Span<FCurve *> fcurves, const FCurveDescriptor &fcurve_descriptor)
{
const FCurve *fcurve = fcurve_find(fcurves.cast<const FCurve *>(), fcurve_descriptor);
return const_cast<FCurve *>(fcurve);
}
FCurve *create_fcurve_for_channel(const FCurveDescriptor &fcurve_descriptor)
{
FCurve *fcu = BKE_fcurve_create();
fcu->rna_path = BLI_strdupn(fcurve_descriptor.rna_path.data(),
fcurve_descriptor.rna_path.size());
fcu->array_index = fcurve_descriptor.array_index;
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
fcu->auto_smoothing = U.auto_smoothing_new;
if (fcurve_descriptor.prop_type.has_value()) {
fcu->flag |= fcurve_flags_for_property_type(*fcurve_descriptor.prop_type);
}
/* Set the fcurve's color mode if needed/able. */
if ((U.keying_flag & KEYING_FLAG_XYZ2RGB) != 0 && fcurve_descriptor.prop_subtype.has_value()) {
switch (*fcurve_descriptor.prop_subtype) {
case PROP_TRANSLATION:
case PROP_XYZ:
case PROP_EULER:
case PROP_COLOR:
case PROP_COORDS:
fcu->color_mode = FCURVE_COLOR_AUTO_RGB;
break;
case PROP_QUATERNION:
fcu->color_mode = FCURVE_COLOR_AUTO_YRGB;
break;
default:
/* Leave the color mode as default. */
break;
}
}
return fcu;
}
eFCurve_Flags fcurve_flags_for_property_type(const PropertyType prop_type)
{
switch (prop_type) {
case PROP_FLOAT:
return eFCurve_Flags(0);
case PROP_INT:
/* Do integer (only 'whole' numbers) interpolation between all points. */
return FCURVE_INT_VALUES;
default:
/* Do 'discrete' (i.e. enum, boolean values which cannot take any intermediate
* values at all) interpolation between all points.
* - however, we must also ensure that evaluated values are only integers still.
*/
return FCURVE_DISCRETE_VALUES | FCURVE_INT_VALUES;
}
}
bool fcurve_delete_keyframe_at_time(FCurve *fcurve, const float time)
{
if (BKE_fcurve_is_protected(fcurve)) {
return false;
}
bool found;
const int index = BKE_fcurve_bezt_binarysearch_index(
fcurve->bezt, time, fcurve->totvert, &found);
if (!found) {
return false;
}
BKE_fcurve_delete_key(fcurve, index);
BKE_fcurve_handles_recalc(fcurve);
return true;
}
bool delete_keyframe_fcurve_legacy(AnimData *adt, FCurve *fcu, float cfra)
{
if (!fcurve_delete_keyframe_at_time(fcu, cfra)) {
return false;
}
/* Empty curves get automatically deleted. */
if (BKE_fcurve_is_empty(fcu)) {
animdata_fcurve_delete(adt, fcu);
}
return true;
}
/* ************************************************** */
/* KEYFRAME INSERTION */
/* -------------- BezTriple Insertion -------------------- */
/* Change the Y position of a keyframe to match the input, adjusting handles. */
static void replace_bezt_keyframe_ypos(BezTriple *dst, const BezTriple *bezt)
{
/* Just change the values when replacing, so as to not overwrite handles. */
float dy = bezt->vec[1][1] - dst->vec[1][1];
/* Just apply delta value change to the handle values. */
dst->vec[0][1] += dy;
dst->vec[1][1] += dy;
dst->vec[2][1] += dy;
dst->f1 = bezt->f1;
dst->f2 = bezt->f2;
dst->f3 = bezt->f3;
/* TODO: perform some other operations? */
}
int insert_bezt_fcurve(FCurve *fcu, const BezTriple *bezt, eInsertKeyFlags flag)
{
int i = 0;
/* Are there already keyframes? */
if (fcu->bezt) {
bool replace;
i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, bezt->vec[1][0], fcu->totvert, &replace);
/* Replace an existing keyframe? */
if (replace) {
/* `i` may in rare cases exceed array bounds. */
if ((i >= 0) && (i < fcu->totvert)) {
if (flag & INSERTKEY_OVERWRITE_FULL) {
fcu->bezt[i] = *bezt;
}
else {
replace_bezt_keyframe_ypos(&fcu->bezt[i], bezt);
}
if (flag & INSERTKEY_CYCLE_AWARE) {
/* If replacing an end point of a cyclic curve without offset,
* modify the other end too. */
if (ELEM(i, 0, fcu->totvert - 1) && BKE_fcurve_get_cycle_type(fcu) == FCU_CYCLE_PERFECT)
{
replace_bezt_keyframe_ypos(&fcu->bezt[i == 0 ? fcu->totvert - 1 : 0], bezt);
}
}
}
}
/* Keyframing modes allow not replacing the keyframe. */
else if ((flag & INSERTKEY_REPLACE) == 0) {
/* Insert new - if we're not restricted to replacing keyframes only. */
BezTriple *newb = MEM_calloc_arrayN<BezTriple>(fcu->totvert + 1, "beztriple");
/* Add the beztriples that should occur before the beztriple to be pasted
* (originally in fcu). */
if (i > 0) {
memcpy(newb, fcu->bezt, i * sizeof(BezTriple));
}
/* Add beztriple to paste at index i. */
*(newb + i) = *bezt;
/* Add the beztriples that occur after the beztriple to be pasted (originally in fcu). */
if (i < fcu->totvert) {
memcpy(newb + i + 1, fcu->bezt + i, (fcu->totvert - i) * sizeof(BezTriple));
}
/* Replace (+ free) old with new, only if necessary to do so. */
MEM_freeN(fcu->bezt);
fcu->bezt = newb;
fcu->totvert++;
}
else {
return -1;
}
}
/* No keyframes yet, but can only add if...
* 1) keyframing modes say that keyframes can only be replaced, so adding new ones won't know
* 2) there are no samples on the curve
* NOTE: maybe we may want to allow this later when doing samples -> bezt conversions,
* but for now, having both is asking for trouble
*/
else if ((flag & INSERTKEY_REPLACE) == 0 && (fcu->fpt == nullptr)) {
/* Create new keyframes array. */
fcu->bezt = MEM_callocN<BezTriple>("beztriple");
*(fcu->bezt) = *bezt;
fcu->totvert = 1;
}
/* Cannot add anything. */
else {
/* Return error code -1 to prevent any misunderstandings. */
return -1;
}
/* We need to return the index, so that some tools which do post-processing can
* detect where we added the BezTriple in the array.
*/
return i;
}
/**
* Update the FCurve to allow insertion of `bezt` without modifying the curve shape.
*
* Checks whether it is necessary to apply Bezier subdivision due to involvement of non-auto
* handles. If necessary, changes `bezt` handles from Auto to Aligned.
*
* \param bezt: key being inserted
* \param prev: keyframe before that key
* \param next: keyframe after that key
*/
static void subdivide_nonauto_handles(const FCurve *fcu,
BezTriple *bezt,
BezTriple *prev,
BezTriple *next)
{
if (prev->ipo != BEZT_IPO_BEZ || bezt->ipo != BEZT_IPO_BEZ) {
return;
}
/* Don't change Vector handles, or completely auto regions. */
const bool bezt_auto = BEZT_IS_AUTOH(bezt) || (bezt->h1 == HD_VECT && bezt->h2 == HD_VECT);
const bool prev_auto = BEZT_IS_AUTOH(prev) || (prev->h2 == HD_VECT);
const bool next_auto = BEZT_IS_AUTOH(next) || (next->h1 == HD_VECT);
if (bezt_auto && prev_auto && next_auto) {
return;
}
/* Subdivide the curve. */
float delta;
if (!BKE_fcurve_bezt_subdivide_handles(bezt, prev, next, &delta)) {
return;
}
/* Decide when to force auto to manual. */
if (!BEZT_IS_AUTOH(bezt)) {
return;
}
if ((prev_auto || next_auto) && fcu->auto_smoothing == FCURVE_SMOOTH_CONT_ACCEL) {
const float hx = bezt->vec[1][0] - bezt->vec[0][0];
const float dx = bezt->vec[1][0] - prev->vec[1][0];
/* This mode always uses 1/3 of key distance for handle x size. */
const bool auto_works_well = fabsf(hx - dx / 3.0f) < 0.001f;
if (auto_works_well) {
return;
}
}
/* Turn off auto mode. */
bezt->h1 = bezt->h2 = HD_ALIGN;
}
void initialize_bezt(BezTriple *beztr,
const float2 position,
const KeyframeSettings &settings,
const eFCurve_Flags fcu_flags)
{
/* Set all three points, for nicer start position.
* NOTE: +/- 1 on vec.x for left and right handles is so that 'free' handles work ok...
*/
beztr->vec[0][0] = position.x - 1.0f;
beztr->vec[0][1] = position.y;
beztr->vec[1][0] = position.x;
beztr->vec[1][1] = position.y;
beztr->vec[2][0] = position.x + 1.0f;
beztr->vec[2][1] = position.y;
beztr->f1 = beztr->f2 = beztr->f3 = SELECT;
beztr->h1 = beztr->h2 = settings.handle;
beztr->ipo = settings.interpolation;
/* Interpolation type used is constrained by the type of values the curve can take. */
if (fcu_flags & FCURVE_DISCRETE_VALUES) {
beztr->ipo = BEZT_IPO_CONST;
}
else if ((beztr->ipo == BEZT_IPO_BEZ) && (fcu_flags & FCURVE_INT_VALUES)) {
beztr->ipo = BEZT_IPO_LIN;
}
/* Set keyframe type value (supplied),
* which should come from the scene settings in most cases. */
BEZKEYTYPE_LVALUE(beztr) = settings.keyframe_type;
/* Set default values for "easing" interpolation mode settings.
* NOTE: Even if these modes aren't currently used, if users switch
* to these later, we want these to work in a sane way out of
* the box.
*/
/* "back" easing - This value used to be used when overshoot=0, but that
* introduced discontinuities in how the param worked. */
beztr->back = 1.70158f;
/* "elastic" easing - Values here were hand-optimized for a default duration of
* ~10 frames (typical motion-graph motion length). */
beztr->amplitude = 0.8f;
beztr->period = 4.1f;
}
/**
* Return whether the given fcurve already evaluates to the same value as the
* proposed keyframe at the keyframe's time.
*
* This is a helper function for determining whether to insert a keyframe or not
* when "only insert needed" is enabled.
*
* NOTE: this does *not* determine whether inserting the keyframe would change
* the fcurve at points other than the keyframe itself. For example, even if
* inserting the key wouldn't change the fcurve's value at the time of the
* keyframe, the resulting changes to bezier interpolation could change the
* fcurve on either side of it. This function intentionally does not account for
* that, since that's not how the "only insert needed" feature is supposed to
* work.
*/
static bool new_key_needed(const FCurve &fcu, const float frame, const float value)
{
if (fcu.totvert == 0) {
return true;
}
bool replace;
const int bezt_index = BKE_fcurve_bezt_binarysearch_index(
fcu.bezt, frame, fcu.totvert, &replace);
if (replace) {
/* If there is already a key, we only need to modify it if the proposed value is different. */
return fcu.bezt[bezt_index].vec[1][1] != value;
}
const int diff_ulp = 32;
const float fcu_eval = evaluate_fcurve(&fcu, frame);
/* No need to insert a key if the same value is already the value of the FCurve at that point. */
if (compare_ff_relative(fcu_eval, value, FLT_EPSILON, diff_ulp)) {
return false;
}
return true;
}
/**
* Move the point where a key is about to be inserted to be inside the main cycle range.
* Returns the type of the cycle if it is enabled and valid.
*/
static float2 remap_cyclic_keyframe_location(const FCurve &fcu,
const eFCU_Cycle_Type type,
float2 position)
{
if (fcu.totvert < 2 || !fcu.bezt) {
return position;
}
if (type == FCU_CYCLE_NONE) {
return position;
}
BezTriple *first = &fcu.bezt[0], *last = &fcu.bezt[fcu.totvert - 1];
const float start = first->vec[1][0], end = last->vec[1][0];
if (start >= end) {
return position;
}
if (position.x < start || position.x > end) {
const float period = end - start;
const float step = floorf((position.x - start) / period);
position.x -= step * period;
if (type == FCU_CYCLE_OFFSET) {
/* Nasty check to handle the case when the modes are different better. */
FMod_Cycles *data = static_cast<FMod_Cycles *>(
static_cast<FModifier *>(fcu.modifiers.first)->data);
short mode = (step >= 0) ? data->after_mode : data->before_mode;
if (mode == FCM_EXTRAPOLATE_CYCLIC_OFFSET) {
position.y -= step * (last->vec[1][1] - first->vec[1][1]);
}
}
}
return position;
}
SingleKeyingResult insert_vert_fcurve(FCurve *fcu,
const float2 position,
const KeyframeSettings &settings,
eInsertKeyFlags flag)
{
BLI_assert(fcu != nullptr);
float2 remapped_position = position;
/* Adjust coordinates for cycle aware insertion. */
if (flag & INSERTKEY_CYCLE_AWARE) {
eFCU_Cycle_Type type = BKE_fcurve_get_cycle_type(fcu);
remapped_position = remap_cyclic_keyframe_location(*fcu, type, position);
if (type != FCU_CYCLE_PERFECT) {
/* Inhibit action from insert_bezt_fcurve unless it's a perfect cycle. */
flag &= ~INSERTKEY_CYCLE_AWARE;
}
}
if ((flag & INSERTKEY_NEEDED) && !new_key_needed(*fcu, remapped_position.x, remapped_position.y))
{
return SingleKeyingResult::NO_KEY_NEEDED;
}
BezTriple beztr = {{{0}}};
initialize_bezt(&beztr, remapped_position, settings, eFCurve_Flags(fcu->flag));
uint oldTot = fcu->totvert;
int a;
/* Add temp beztriple to keyframes. */
a = insert_bezt_fcurve(fcu, &beztr, flag);
BKE_fcurve_active_keyframe_set(fcu, &fcu->bezt[a]);
/* Key insertion failed. */
if (a < 0) {
/* TODO: we need more info from `insert_bezt_fcurve()` called above to
* return a more specific failure. */
return SingleKeyingResult::UNKNOWN_FAILURE;
}
/* Set handle-type and interpolation. */
if ((fcu->totvert > 2) && (flag & INSERTKEY_REPLACE) == 0) {
BezTriple *bezt = (fcu->bezt + a);
/* Set interpolation from previous (if available),
* but only if we didn't just replace some keyframe:
* - Replacement is indicated by no-change in number of verts.
* - When replacing, the user may have specified some interpolation that should be kept.
*/
if (fcu->totvert > oldTot) {
if (a > 0) {
bezt->ipo = (bezt - 1)->ipo;
}
else if (a < fcu->totvert - 1) {
bezt->ipo = (bezt + 1)->ipo;
}
if (0 < a && a < (fcu->totvert - 1) && (flag & INSERTKEY_OVERWRITE_FULL) == 0) {
subdivide_nonauto_handles(fcu, bezt, bezt - 1, bezt + 1);
}
}
}
/* Don't recalculate handles if fast is set.
* - this is a hack to make importers faster
* - we may calculate twice (due to auto-handle needing to be calculated twice)
*/
if ((flag & INSERTKEY_FAST) == 0) {
BKE_fcurve_handles_recalc(fcu);
}
/* Return the index at which the keyframe was added. */
return SingleKeyingResult::SUCCESS;
}
void sample_fcurve_segment(const 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::ALL: {
BKE_fcurve_delete_keys_all(fcu);
break;
}
case BakeCurveRemove::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);
/* #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::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 = MEM_calloc_arrayN<float>(sample_count, "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::NONE) {
remove_fcurve_key_range(fcu, range, remove_existing);
}
BezTriple *baked_keys = MEM_calloc_arrayN<BezTriple>(sample_count, "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)
{
const 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 = MEM_calloc_arrayN<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, INSERTKEY_NOFLAGS);
}
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);
}
bool fcurve_frame_has_keyframe(const FCurve *fcu, const float frame)
{
if (ELEM(nullptr, fcu, fcu->bezt)) {
return false;
}
if ((fcu->flag & FCURVE_MUTED) == 0) {
bool replace;
const int i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, frame, fcu->totvert, &replace);
/* #BKE_fcurve_bezt_binarysearch_index will set replace to be 0 or 1
* - obviously, 1 represents a match
*/
if (replace) {
/* `i` may in rare cases exceed array bounds. */
if ((i >= 0) && (i < fcu->totvert)) {
return true;
}
}
}
return false;
}
} // namespace blender::animrig