/* SPDX-FileCopyrightText: 2023 Blender Authors * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup animrig */ #include #include #include #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 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 fcurves, const FCurveDescriptor &fcurve_descriptor) { const FCurve *fcurve = fcurve_find(fcurves.cast(), fcurve_descriptor); return const_cast(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{}; 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 (!fcurve || 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_new_array_zeroed(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_delete(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_new_zeroed("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 = BEZT_FLAG_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( static_cast(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_new_array_zeroed(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_new_array_zeroed(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_delete(fcu->bezt); } MEM_delete(baked_keys); fcu->bezt = merged_bezt; fcu->totvert = merged_size; MEM_delete(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_new_array_zeroed(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++) { animrig::insert_vert_fcurve(fcu, {fp->frame, fp->val}, settings, INSERTKEY_NOFLAGS); } MEM_delete(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