Refactor: fcurve bounds functions #105177

Merged
Christoph Lendenfeld merged 16 commits from ChrisLend/blender:refactor_fcurve_bounds into main 2023-03-10 11:33:21 +01:00
9 changed files with 394 additions and 237 deletions

View File

@ -366,24 +366,22 @@ int BKE_fcurve_pathcache_find_array(struct FCurvePathCache *fcache,
int fcurve_result_len);
/**
* Calculate the extents of F-Curve's keyframes.
* Calculate the x range of the given F-Curve's data.
* \return true if a range has been found.
ChrisLend marked this conversation as resolved

Document what the return value means.

Document what the return value means.
*/
ChrisLend marked this conversation as resolved

Return parameters should be prefixed with r_. I think this is an excellent opportunity to tackle that too.

And it's weird, French is not my favourite language, I'm not even that good at it. But still when I read do_sel_only I'm thinking "only salt?". No strong opinion here, but at the risk of doing even more in the same patch, I wouldn't mind if that parameter were renamed to use_selected_keys_only. I'll leave the final decision on whether to include that change or not to you though.

Return parameters should be prefixed with `r_`. I think this is an excellent opportunity to tackle that too. And it's weird, French is not my favourite language, I'm not even that good at it. But still when I read `do_sel_only` I'm thinking "only salt?". No strong opinion here, but at the risk of doing even more in the same patch, I wouldn't mind if that parameter were renamed to `use_selected_keys_only`. I'll leave the final decision on whether to include that change or not to you though.
bool BKE_fcurve_calc_range(
const struct FCurve *fcu, float *min, float *max, bool do_sel_only, bool do_min_length);
bool BKE_fcurve_calc_range(const struct FCurve *fcu, float *r_min, float *r_max, bool selected_keys_only);
/**
* Calculate the extents of F-Curve's data.
* \param range Only calculate the bounds of the FCurve in the given range.
* Calculate the x and y extents of F-Curve's data.
* \param frame_range Only calculate the bounds of the FCurve in the given range.
* Does the full range if NULL.
* \return true if the bounds have been found.
*/
bool BKE_fcurve_calc_bounds(const struct FCurve *fcu,
float *xmin,
float *xmax,
float *ymin,
float *ymax,
bool do_sel_only,
bool selected_keys_only,
bool include_handles,
const float range[2]);
const float frame_range[2],
struct rctf *r_bounds);
/**
* Return an array of keyed frames, rounded to `interval`.

View File

@ -1408,7 +1408,7 @@ void calc_action_range(const bAction *act, float *start, float *end, short incl_
* single-keyframe curves will increase the overall length by
* a phantom frame (#50354)
*/
BKE_fcurve_calc_range(fcu, &nmin, &nmax, false, false);
BKE_fcurve_calc_range(fcu, &nmin, &nmax, false);
/* compare to the running tally */
min = min_ff(min, nmin);

View File

@ -556,252 +556,236 @@ int BKE_fcurve_bezt_binarysearch_index(const BezTriple array[],
/* ...................................... */
/* Helper for calc_fcurve_* functions -> find first and last BezTriple to be used. */
static bool get_fcurve_end_keyframes(const FCurve *fcu,
BezTriple **first,
BezTriple **last,
const bool do_sel_only,
const float range[2])
/* Get the first and last index to the bezt array that satisfies the given parameters.
* \param selected_keys_only Only accept indices of bezt that are selected. Is a subset of
* frame_range. \param frame_range Only consider keyframes in that frame interval. Can be NULL.
*/
static bool get_bounding_bezt_indices(const FCurve *fcu,
const bool selected_keys_only,
const float frame_range[2],
int *r_first,
int *r_last)
{
bool found = false;
/* Init outputs. */
*first = NULL;
*last = NULL;
/* Sanity checks. */
if (fcu->bezt == NULL) {
return found;
return false;
}
int first_index = 0;
int last_index = fcu->totvert - 1;
*r_first = 0;
*r_last = fcu->totvert - 1;
if (range != NULL) {
bool found = false;
if (frame_range != NULL) {
/* If a range is passed in find the first and last keyframe within that range. */
bool replace = false;
first_index = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, range[0], fcu->totvert, &replace);
last_index = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, range[1], fcu->totvert, &replace);
*r_first = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, frame_range[0], fcu->totvert, &replace);
*r_last = BKE_fcurve_bezt_binarysearch_index(
fcu->bezt, frame_range[1], fcu->totvert, &replace);
/* If first and last index are the same, no keyframes were found in the range. */
if (first_index == last_index) {
return found;
if (*r_first == *r_last) {
return false;
}
/* The binary search returns an index where a keyframe would be inserted,
* so it needs to be clamped to ensure it is in range of the array. */
first_index = clamp_i(first_index, 0, fcu->totvert - 1);
last_index = clamp_i(last_index - 1, 0, fcu->totvert - 1);
*r_first = clamp_i(*r_first, 0, fcu->totvert - 1);
*r_last = clamp_i(*r_last - 1, 0, fcu->totvert - 1);
}
/* Only include selected items? */
if (do_sel_only) {
if (selected_keys_only) {
/* Find first selected. */
for (int i = first_index; i <= last_index; i++) {
for (int i = *r_first; i <= *r_last; i++) {
BezTriple *bezt = &fcu->bezt[i];
if (BEZT_ISSEL_ANY(bezt)) {
*first = bezt;
*r_first = i;
found = true;
break;
}
}
/* Find last selected. */
for (int i = last_index; i >= first_index; i--) {
for (int i = *r_last; i >= *r_first; i--) {
BezTriple *bezt = &fcu->bezt[i];
if (BEZT_ISSEL_ANY(bezt)) {
*last = bezt;
*r_last = i;
found = true;
break;
}
}
}
else {
*first = &fcu->bezt[first_index];
*last = &fcu->bezt[last_index];
found = true;
}
return found;
}
bool BKE_fcurve_calc_bounds(const FCurve *fcu,
float *xmin,
float *xmax,
float *ymin,
float *ymax,
const bool do_sel_only,
const bool include_handles,
const float range[2])
static void calculate_bezt_bounds_x(BezTriple *bezt_array,
const int index_range[2],
const bool include_handles,
float *r_min,
float *r_max)
{
float xminv = 999999999.0f, xmaxv = -999999999.0f;
float yminv = 999999999.0f, ymaxv = -999999999.0f;
bool foundvert = false;
*r_min = bezt_array[index_range[0]].vec[1][0];
*r_max = bezt_array[index_range[1]].vec[1][0];
ChrisLend marked this conversation as resolved

I think this should be using index_range[1].

I think this should be using `index_range[1]`.
const bool use_range = range != NULL;
if (fcu->totvert) {
if (fcu->bezt) {
if (xmin || xmax) {
BezTriple *bezt_first = NULL, *bezt_last = NULL;
foundvert = get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only, range);
if (bezt_first) {
BLI_assert(bezt_last != NULL);
foundvert = true;
if (include_handles) {
xminv = min_fff(xminv, bezt_first->vec[0][0], bezt_first->vec[1][0]);
xmaxv = max_fff(xmaxv, bezt_last->vec[1][0], bezt_last->vec[2][0]);
}
else {
xminv = min_ff(xminv, bezt_first->vec[1][0]);
xmaxv = max_ff(xmaxv, bezt_last->vec[1][0]);
}
}
}
/* Only loop over keyframes to find extents for values if needed. */
if (ymin || ymax) {
BezTriple *bezt, *prevbezt = NULL;
int i;
for (bezt = fcu->bezt, i = 0; i < fcu->totvert; prevbezt = bezt, bezt++, i++) {
if (use_range && (bezt->vec[1][0] < range[0] || bezt->vec[1][0] > range[1])) {
continue;
}
if ((do_sel_only == false) || BEZT_ISSEL_ANY(bezt)) {
/* Keyframe itself. */
yminv = min_ff(yminv, bezt->vec[1][1]);
ymaxv = max_ff(ymaxv, bezt->vec[1][1]);
if (include_handles) {
/* Left handle - only if applicable.
* NOTE: for the very first keyframe,
* the left handle actually has no bearings on anything. */
if (prevbezt && (prevbezt->ipo == BEZT_IPO_BEZ)) {
yminv = min_ff(yminv, bezt->vec[0][1]);
ymaxv = max_ff(ymaxv, bezt->vec[0][1]);
}
/* Right handle - only if applicable. */
if (bezt->ipo == BEZT_IPO_BEZ) {
yminv = min_ff(yminv, bezt->vec[2][1]);
ymaxv = max_ff(ymaxv, bezt->vec[2][1]);
}
}
foundvert = true;
}
}
}
}
else if (fcu->fpt) {
/* Frame range can be directly calculated from end verts. */
if (xmin || xmax) {
xminv = min_ff(xminv, fcu->fpt[0].vec[0]);
xmaxv = max_ff(xmaxv, fcu->fpt[fcu->totvert - 1].vec[0]);
}
/* Only loop over keyframes to find extents for values if needed. */
if (ymin || ymax) {
int i = 0;
for (FPoint *fpt = fcu->fpt; i < fcu->totvert; fpt++, i++) {
if (fpt->vec[1] < yminv) {
yminv = fpt->vec[1];
}
if (fpt->vec[1] > ymaxv) {
ymaxv = fpt->vec[1];
}
foundvert = true;
}
}
if (include_handles) {
/* Need to check all handles because they might extend beyond their neighboring keys. */
for (int i = index_range[0]; i <= index_range[1]; i++) {
const BezTriple *bezt = &bezt_array[i];
dr.sybren marked this conversation as resolved

bezt can be const, same in the function below.

`bezt` can be `const`, same in the function below.
Review

Better to not make a copy at all here. BezTriple is a rather large struct. In C the best option is const BezTriple *bezt = &bezt_array[i]; The compiler probably optimizes that but it's a good way to show intentions too.

Better to not make a copy at all here. `BezTriple` is a rather large struct. In C the best option is `const BezTriple *bezt = &bezt_array[i];` The compiler probably optimizes that but it's a good way to show intentions too.

I've made it a Beztriple * now

I've made it a `Beztriple *` now
*r_min = min_fff(*r_min, bezt->vec[0][0], bezt->vec[1][0]);
*r_max = max_fff(*r_max, bezt->vec[1][0], bezt->vec[2][0]);
}
}
if (foundvert) {
if (xmin) {
*xmin = xminv;
}
if (xmax) {
*xmax = xmaxv;
}
if (ymin) {
*ymin = yminv;
}
if (ymax) {
*ymax = ymaxv;
}
}
else {
if (G.debug & G_DEBUG) {
printf("F-Curve calc bounds didn't find anything, so assuming minimum bounds of 1.0\n");
}
if (xmin) {
*xmin = use_range ? range[0] : 0.0f;
}
if (xmax) {
*xmax = use_range ? range[1] : 1.0f;
}
if (ymin) {
*ymin = 0.0f;
}
if (ymax) {
*ymax = 1.0f;
}
}
return foundvert;
}
ChrisLend marked this conversation as resolved

If I'm right about the initial assignment to *r_max using index_range[1], this entire else block can be removed.

If I'm right about the initial assignment to `*r_max` using `index_range[1]`, this entire `else` block can be removed.
bool BKE_fcurve_calc_range(
const FCurve *fcu, float *start, float *end, const bool do_sel_only, const bool do_min_length)
static void calculate_bezt_bounds_y(BezTriple *bezt_array,
const int index_range[2],
const bool selected_keys_only,
const bool include_handles,
float *r_min,
float *r_max)
{
float min = 999999999.0f, max = -999999999.0f;
*r_min = bezt_array[index_range[0]].vec[1][1];
*r_max = bezt_array[index_range[0]].vec[1][1];
for (int i = index_range[0]; i <= index_range[1]; i++) {
const BezTriple *bezt = &bezt_array[i];
if (selected_keys_only && !BEZT_ISSEL_ANY(bezt)) {
continue;
}
*r_min = min_ff(*r_min, bezt->vec[1][1]);
*r_max = max_ff(*r_max, bezt->vec[1][1]);
if (include_handles) {
*r_min = min_fff(*r_min, bezt->vec[0][1], bezt->vec[2][1]);
*r_max = max_fff(*r_max, bezt->vec[0][1], bezt->vec[2][1]);
}
}
}
static bool calculate_bezt_bounds(const FCurve *fcu,
dr.sybren marked this conversation as resolved

This is a tricky one. If the idea is to zoom the FCurve so that all the keys and handles are on screen, it doesn't matter that the left handle has no meaning; an animator may still want to interact with it in order to edit the animation. And with, for example, "Aligned" handle type, manipulating the left handle will move the right one, and thus still has an effect.

This is a tricky one. If the idea is to zoom the FCurve so that all the keys and handles are on screen, it doesn't matter that the left handle has no meaning; an animator may still want to interact with it in order to edit the animation. And with, for example, "Aligned" handle type, manipulating the left handle will move the right one, and thus still has an effect.

I was hesitant to change that because I wasn't quite sure of the purpose
I removed that logic completely now. IMO this function shouldn't concern itself with the visibility/functionality of handles.
Controversial though :)

I was hesitant to change that because I wasn't quite sure of the purpose I removed that logic completely now. IMO this function shouldn't concern itself with the visibility/functionality of handles. Controversial though :)
const bool selected_keys_only,
const bool include_handles,
const float frame_range[2],
rctf *r_bounds)
{
int index_range[2];
const bool found_indices = get_bounding_bezt_indices(
fcu, selected_keys_only, frame_range, &index_range[0], &index_range[1]);
if (!found_indices) {
return false;
}
calculate_bezt_bounds_x(
fcu->bezt, index_range, include_handles, &r_bounds->xmin, &r_bounds->xmax);
calculate_bezt_bounds_y(fcu->bezt,
index_range,
selected_keys_only,
include_handles,
&r_bounds->ymin,
&r_bounds->ymax);
return true;
}
static bool calculate_fpt_bounds(const FCurve *fcu, const float frame_range[2], rctf *r_bounds)
{
r_bounds->xmin = INFINITY;
ChrisLend marked this conversation as resolved

I think this nesting of conditionals is too deep. Better to split up the function into one for Bezier curves and one for baked points.

I think this nesting of conditionals is too deep. Better to split up the function into one for Bezier curves and one for baked points.
r_bounds->xmax = -INFINITY;
r_bounds->ymin = INFINITY;
r_bounds->ymax = -INFINITY;
const int first_index = 0;
const int last_index = fcu->totvert - 1;
int start_index = first_index;
int end_index = last_index;
if (frame_range != NULL) {
/* Start index can be calculated because fpt has a key on every full frame. */
const float start_index_f = frame_range[0] - fcu->fpt[0].vec[0];
const float end_index_f = start_index_f + frame_range[1] - frame_range[0];
if (start_index_f > fcu->totvert - 1 || end_index_f < 0) {
/* Range is outside of keyframe samples. */
return false;
dr.sybren marked this conversation as resolved

Since there are no handles, you can use the ordering of the points and do a binary search for the start of the range. You can then immediately handle the xmin, and when the last point in the range is found, update the xmax. Per point you then only need to consider the y-coordinates, cutting the number of operations in half.

Since there are no handles, you can use the ordering of the points and do a binary search for the start of the range. You can then immediately handle the `xmin`, and when the last point in the range is found, update the `xmax`. Per point you then only need to consider the y-coordinates, cutting the number of operations in half.

instead of doing a binary search i calculate the index based on the assumption that a baked fcurve has a sample on every full frame.

instead of doing a binary search i calculate the index based on the assumption that a baked fcurve has a sample on every full frame.
}
/* Range might be partially covering keyframe samples. */
ChrisLend marked this conversation as resolved

Initialize r_bounds to ±∞ (so all minimum values to and all maximum values to -∞). Those are valid IEEE 754 floating point values, and work well with the min/max functions. That'll remove a boolean, a condition, and half of the lines of code.

Initialize `r_bounds` to `±∞` (so all minimum values to `∞` and all maximum values to `-∞`). Those are valid IEEE 754 floating point values, and work well with the min/max functions. That'll remove a boolean, a condition, and half of the lines of code.
start_index = clamp_i(start_index_f, 0, fcu->totvert - 1);
end_index = clamp_i(end_index_f, 0, fcu->totvert - 1);
}
/* X range can be directly calculated from end verts. */
r_bounds->xmin = fcu->fpt[start_index].vec[0];
r_bounds->xmax = fcu->fpt[end_index].vec[0];
for (int i = start_index; i <= end_index; i++) {
r_bounds->ymin = min_ff(r_bounds->ymin, fcu->fpt[i].vec[1]);
r_bounds->ymax = max_ff(r_bounds->ymax, fcu->fpt[i].vec[1]);
}
return BLI_rctf_is_valid(r_bounds);
ChrisLend marked this conversation as resolved

Here you can return r_bounds->xmin <= r_bounds->xmax.

Here you can return `r_bounds->xmin <= r_bounds->xmax`.
}
bool BKE_fcurve_calc_bounds(const FCurve *fcu,

A different structure of the function could work well here:

  1. Find the start/end index (either 0 resp. totvert - 1, or the result of a binary search)
  2. Loop over the points in the index range to find the min/max y.
A different structure of the function could work well here: 1. Find the start/end index (either 0 resp. `totvert - 1`, or the result of a binary search) 2. Loop over the points in the index range to find the min/max y.

can you clarify that, is that in regards to finding the start index when using a frame range?

can you clarify that, is that in regards to finding the start index when using a frame range?

I was thinking about code like this:

static bool calculate_fpt_bounds(const FCurve *fcu, const float frame_range[2], rctf *r_bounds)
{
  r_bounds->xmin = INFINITY;
  r_bounds->xmax = -INFINITY;
  r_bounds->ymin = INFINITY;
  r_bounds->ymax = -INFINITY;

  const int first_index = 0;
  const int last_index = fcu->totvert - 1;
  int start_index = first_index;
  int end_index = last_index;

  if (frame_range != NULL) {
    /* Start index can be calculated because fpt has a key on every full frame. */
    const float start_index_f = frame_range[0] - fcu->fpt[0].vec[0];
    const float end_index_f = start_index_f + frame_range[1] - frame_range[0];

    if (start_index_f > last_index || end_index_f < first_index) {
      /* Range is outside of keyframe samples. */
      return false;
    }

    /* Range might be partially covering keyframe samples. */
    start_index = clamp_i(start_index_f, first_index, last_index);
    end_index = clamp_i(end_index_f, first_index, last_index);
  }

  /* X range can be directly calculated from end verts. */
  r_bounds->xmin = fcu->fpt[start_index].vec[0];
  r_bounds->xmax = fcu->fpt[end_index].vec[0];

  for (int i = start_index; i <= end_index; i++) {
    r_bounds->ymin = min_ff(r_bounds->ymin, fcu->fpt[i].vec[1]);
    r_bounds->ymax = max_ff(r_bounds->ymax, fcu->fpt[i].vec[1]);
  }

  return BLI_rctf_is_valid(r_bounds);
}

This uses INFINITY instead of FLT_MAX so that valid and invalid values can always be distinguished. And it separates "determine the start/endices" from "finding the bounds".

I'm not 100% about the name of start_index_f, maybe range_start_index would be better.

Update: using BLI_rctf_is_valid() instead of writing all of that out.

I was thinking about code like this: ```c static bool calculate_fpt_bounds(const FCurve *fcu, const float frame_range[2], rctf *r_bounds) { r_bounds->xmin = INFINITY; r_bounds->xmax = -INFINITY; r_bounds->ymin = INFINITY; r_bounds->ymax = -INFINITY; const int first_index = 0; const int last_index = fcu->totvert - 1; int start_index = first_index; int end_index = last_index; if (frame_range != NULL) { /* Start index can be calculated because fpt has a key on every full frame. */ const float start_index_f = frame_range[0] - fcu->fpt[0].vec[0]; const float end_index_f = start_index_f + frame_range[1] - frame_range[0]; if (start_index_f > last_index || end_index_f < first_index) { /* Range is outside of keyframe samples. */ return false; } /* Range might be partially covering keyframe samples. */ start_index = clamp_i(start_index_f, first_index, last_index); end_index = clamp_i(end_index_f, first_index, last_index); } /* X range can be directly calculated from end verts. */ r_bounds->xmin = fcu->fpt[start_index].vec[0]; r_bounds->xmax = fcu->fpt[end_index].vec[0]; for (int i = start_index; i <= end_index; i++) { r_bounds->ymin = min_ff(r_bounds->ymin, fcu->fpt[i].vec[1]); r_bounds->ymax = max_ff(r_bounds->ymax, fcu->fpt[i].vec[1]); } return BLI_rctf_is_valid(r_bounds); } ``` This uses `INFINITY` instead of `FLT_MAX` so that valid and invalid values can always be distinguished. And it separates "determine the start/endices" from "finding the bounds". I'm not 100% about the name of `start_index_f`, maybe `range_start_index` would be better. **Update:** using `BLI_rctf_is_valid()` instead of writing all of that out.

thanks got it
and changed to that code

thanks got it and changed to that code
const bool selected_keys_only,
const bool include_handles,
const float frame_range[2],
rctf *r_bounds)
{
if (fcu->totvert == 0) {
return false;
}
if (fcu->bezt) {
const bool found_bounds = calculate_bezt_bounds(
fcu, selected_keys_only, include_handles, frame_range, r_bounds);
return found_bounds;
}
if (fcu->fpt) {

No else after return; clang-tidy will complain about this.

No `else` after `return`; `clang-tidy` will complain about this.
const bool founds_bounds = calculate_fpt_bounds(fcu, frame_range, r_bounds);
return founds_bounds;
}
return false;
}
bool BKE_fcurve_calc_range(const FCurve *fcu,
float *r_start,
float *r_end,
const bool selected_keys_only)
{
float min, max = 0.0f;
bool foundvert = false;
if (fcu->totvert) {
if (fcu->bezt) {
BezTriple *bezt_first = NULL, *bezt_last = NULL;
/* Get endpoint keyframes. */
get_fcurve_end_keyframes(fcu, &bezt_first, &bezt_last, do_sel_only, NULL);
if (bezt_first) {
BLI_assert(bezt_last != NULL);
min = min_ff(min, bezt_first->vec[1][0]);
max = max_ff(max, bezt_last->vec[1][0]);
foundvert = true;
}
}
else if (fcu->fpt) {
min = min_ff(min, fcu->fpt[0].vec[0]);
max = max_ff(max, fcu->fpt[fcu->totvert - 1].vec[0]);
foundvert = true;
}
if (fcu->totvert == 0) {
return false;
}
if (foundvert == false) {
min = max = 0.0f;
}
if (do_min_length) {
/* Minimum length is 1 frame. */
if (min == max) {
max += 1.0f;
if (fcu->bezt) {
int index_range[2];
foundvert = get_bounding_bezt_indices(
fcu, selected_keys_only, NULL, &index_range[0], &index_range[1]);
if (!foundvert) {
return false;
}
const bool include_handles = false;
calculate_bezt_bounds_x(fcu->bezt, index_range, include_handles, &min, &max);
}
else if (fcu->fpt) {
min = fcu->fpt[0].vec[0];
max = fcu->fpt[fcu->totvert - 1].vec[0];
foundvert = true;
}
*start = min;
*end = max;
*r_start = min;
*r_end = max;
return foundvert;
}

View File

@ -346,4 +346,189 @@ TEST(BKE_fcurve, BKE_fcurve_keyframe_move_value_with_handles)
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_calc_range)
{
FCurve *fcu = BKE_fcurve_create();
insert_vert_fcurve(fcu, 1.0f, 7.5f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 4.0f, -15.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 8.0f, 15.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 14.0f, 8.2f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 18.2f, -20.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
for (int i = 0; i < fcu->totvert; i++) {
fcu->bezt[i].f1 &= ~SELECT;
fcu->bezt[i].f2 &= ~SELECT;
fcu->bezt[i].f3 &= ~SELECT;
}
float min, max;
bool success;
/* All keys. */
success = BKE_fcurve_calc_range(fcu, &min, &max, false);
EXPECT_TRUE(success) << "A non-empty FCurve should have a range.";
ChrisLend marked this conversation as resolved

Avoid plain EXPECT_TRUE/EXPECT_FALSE. You can add an explanation with stream operators, like this:

EXPECT_TRUE(success) << "A non-empty FCurve should have a range";
Avoid plain `EXPECT_TRUE`/`EXPECT_FALSE`. You can add an explanation with stream operators, like this: ```cpp EXPECT_TRUE(success) << "A non-empty FCurve should have a range"; ```
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[1][0], min);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[1][0], max);
/* Only selected. */
success = BKE_fcurve_calc_range(fcu, &min, &max, true);
EXPECT_FALSE(success)
<< "Using selected keyframes only should not find a range if nothing is selected.";
fcu->bezt[1].f2 |= SELECT;
fcu->bezt[3].f2 |= SELECT;
success = BKE_fcurve_calc_range(fcu, &min, &max, true);
EXPECT_TRUE(success) << "Range of selected keyframes should have been found.";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], min);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[1][0], max);
/* Curve samples. */
const int sample_start = 1;
const int sample_end = 20;
fcurve_store_samples(fcu, NULL, sample_start, sample_end, fcurve_samplingcb_evalcurve);
success = BKE_fcurve_calc_range(fcu, &min, &max, true);
EXPECT_TRUE(success) << "FCurve samples should have a range.";
EXPECT_FLOAT_EQ(sample_start, min);
EXPECT_FLOAT_EQ(sample_end, max);
BKE_fcurve_free(fcu);
}
TEST(BKE_fcurve, BKE_fcurve_calc_bounds)
{
FCurve *fcu = BKE_fcurve_create();
insert_vert_fcurve(fcu, 1.0f, 7.5f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 4.0f, -15.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 8.0f, 15.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 14.0f, 8.2f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
insert_vert_fcurve(fcu, 18.2f, -20.0f, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NO_USERPREF);
for (int i = 0; i < fcu->totvert; i++) {
fcu->bezt[i].f1 &= ~SELECT;
fcu->bezt[i].f2 &= ~SELECT;
fcu->bezt[i].f3 &= ~SELECT;
}
fcu->bezt[0].vec[0][0] = -5.0f;
fcu->bezt[4].vec[2][0] = 25.0f;
rctf bounds;
bool success;
/* All keys. */
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "A non-empty FCurve should have bounds.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[1][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[1][0], bounds.xmax);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[1][1], bounds.ymin);
EXPECT_FLOAT_EQ(fcu->bezt[2].vec[1][1], bounds.ymax);
/* Only selected. */
success = BKE_fcurve_calc_bounds(
fcu, true /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_FALSE(success)
<< "Using selected keyframes only should not find bounds if nothing is selected.";
fcu->bezt[1].f2 |= SELECT;
fcu->bezt[3].f2 |= SELECT;
success = BKE_fcurve_calc_bounds(
fcu, true /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "Selected keys should have been found.";
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[1][0], bounds.xmax);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], bounds.ymin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[1][1], bounds.ymax);
/* Including handles. */
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, true /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "A non-empty FCurve should have bounds including handles.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[0][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[2][0], bounds.xmax);
EXPECT_FLOAT_EQ(fcu->bezt[4].vec[1][1], bounds.ymin);
EXPECT_FLOAT_EQ(fcu->bezt[2].vec[1][1], bounds.ymax);
/* Range. */
float range[2];
range[0] = 25;
range[1] = 30;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_FALSE(success) << "A frame range outside the range of keyframes should not find bounds.";
range[0] = 0;
range[1] = 18.2f;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success) << "A frame range within the range of keyframes should find bounds.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[1][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[1][0], bounds.xmax);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], bounds.ymin);
EXPECT_FLOAT_EQ(fcu->bezt[2].vec[1][1], bounds.ymax);
/* Range and handles. */
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, true /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success)
<< "A frame range within the range of keyframes should find bounds with handles.";
EXPECT_FLOAT_EQ(fcu->bezt[0].vec[0][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[2][0], bounds.xmax);
EXPECT_FLOAT_EQ(fcu->bezt[1].vec[1][1], bounds.ymin);
EXPECT_FLOAT_EQ(fcu->bezt[2].vec[1][1], bounds.ymax);
/* Range, handles and only selection. */
range[0] = 8.0f;
range[1] = 18.2f;
success = BKE_fcurve_calc_bounds(
fcu, true /* sel only */, true /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success)
<< "A frame range within the range of keyframes should find bounds of selected keyframes.";
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[0][0], bounds.xmin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[2][0], bounds.xmax);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[2][1], bounds.ymin);
EXPECT_FLOAT_EQ(fcu->bezt[3].vec[0][1], bounds.ymax);
/* Curve samples. */
const int sample_start = 1;
const int sample_end = 20;
fcurve_store_samples(fcu, NULL, sample_start, sample_end, fcurve_samplingcb_evalcurve);
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, NULL /* frame range */, &bounds);
EXPECT_TRUE(success) << "FCurve samples should have a range.";
EXPECT_FLOAT_EQ(sample_start, bounds.xmin);
EXPECT_FLOAT_EQ(sample_end, bounds.xmax);
EXPECT_FLOAT_EQ(-20.0f, bounds.ymin);
EXPECT_FLOAT_EQ(15.0f, bounds.ymax);
range[0] = 8.0f;
range[1] = 20.0f;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_TRUE(success) << "FCurve samples should have a range.";
EXPECT_FLOAT_EQ(range[0], bounds.xmin);
EXPECT_FLOAT_EQ(range[1], bounds.xmax);
EXPECT_FLOAT_EQ(-20.0f, bounds.ymin);
EXPECT_FLOAT_EQ(15.0f, bounds.ymax);
range[0] = 20.1f;
range[1] = 30.0f;
success = BKE_fcurve_calc_bounds(
fcu, false /* sel only */, false /* include handles */, range /* frame range */, &bounds);
EXPECT_FALSE(success)
<< "A frame range outside the range of keyframe samples should not have bounds.";
BKE_fcurve_free(fcu);
}
} // namespace blender::bke::tests

View File

@ -3651,17 +3651,7 @@ static bool get_normalized_fcurve_bounds(FCurve *fcu,
rctf *r_bounds)
{
const bool fcu_selection_only = false;
const bool found_bounds = BKE_fcurve_calc_bounds(fcu,
&r_bounds->xmin,
&r_bounds->xmax,
&r_bounds->ymin,
&r_bounds->ymax,
fcu_selection_only,
include_handles,
range);
if (!found_bounds) {
return false;
}
BKE_fcurve_calc_bounds(fcu, fcu_selection_only, include_handles, range, r_bounds);
const short mapping_flag = ANIM_get_normalization_flags(ac);
@ -3736,8 +3726,8 @@ static void get_view_range(Scene *scene, const bool use_preview_range, float r_r
r_range[1] = scene->r.pefra;
}
else {
r_range[0] = -FLT_MAX;
r_range[1] = FLT_MAX;
r_range[0] = scene->r.sfra;
r_range[1] = scene->r.efra;
}
}
@ -3879,6 +3869,7 @@ static int graphkeys_channel_view_pick_invoke(bContext *C, wmOperator *op, const
float range[2];
const bool use_preview_range = RNA_boolean_get(op->ptr, "use_preview_range");
get_view_range(ac.scene, use_preview_range, range);
rctf bounds;

View File

@ -203,7 +203,7 @@ static bool get_keyframe_extents(bAnimContext *ac, float *min, float *max, const
float tmin, tmax;
/* get range and apply necessary scaling before processing */
if (BKE_fcurve_calc_range(fcu, &tmin, &tmax, onlySel, false)) {
if (BKE_fcurve_calc_range(fcu, &tmin, &tmax, onlySel)) {
if (adt) {
tmin = BKE_nla_tweakedit_remap(adt, tmin, NLATIME_CONVERT_MAP);

View File

@ -632,7 +632,7 @@ static void draw_fcurve_curve(bAnimContext *ac,
if (!draw_extrapolation) {
float fcu_start = 0;
float fcu_end = 0;
BKE_fcurve_calc_range(fcu_, &fcu_start, &fcu_end, false, false);
BKE_fcurve_calc_range(fcu_, &fcu_start, &fcu_end, false);
fcu_start = BKE_nla_tweakedit_remap(adt, fcu_start, NLATIME_CONVERT_MAP);
fcu_end = BKE_nla_tweakedit_remap(adt, fcu_end, NLATIME_CONVERT_MAP);

View File

@ -85,40 +85,39 @@ void get_graph_keyframe_extents(bAnimContext *ac,
for (ale = anim_data.first; ale; ale = ale->next) {
AnimData *adt = ANIM_nla_mapping_get(ac, ale);
FCurve *fcu = (FCurve *)ale->key_data;
float txmin, txmax, tymin, tymax;
rctf bounds;
float unitFac, offset;
/* Get range. */
if (BKE_fcurve_calc_bounds(
fcu, &txmin, &txmax, &tymin, &tymax, do_sel_only, include_handles, NULL)) {
if (BKE_fcurve_calc_bounds(fcu, do_sel_only, include_handles, NULL, &bounds)) {
short mapping_flag = ANIM_get_normalization_flags(ac);
/* Apply NLA scaling. */
if (adt) {
txmin = BKE_nla_tweakedit_remap(adt, txmin, NLATIME_CONVERT_MAP);
txmax = BKE_nla_tweakedit_remap(adt, txmax, NLATIME_CONVERT_MAP);
bounds.xmin = BKE_nla_tweakedit_remap(adt, bounds.xmin, NLATIME_CONVERT_MAP);
bounds.xmax = BKE_nla_tweakedit_remap(adt, bounds.xmax, NLATIME_CONVERT_MAP);
}
/* Apply unit corrections. */
unitFac = ANIM_unit_mapping_get_factor(ac->scene, ale->id, fcu, mapping_flag, &offset);
tymin += offset;
tymax += offset;
tymin *= unitFac;
tymax *= unitFac;
bounds.ymin += offset;
bounds.ymax += offset;
bounds.ymin *= unitFac;
bounds.ymax *= unitFac;
/* Try to set cur using these values, if they're more extreme than previously set values.
*/
if ((xmin) && (txmin < *xmin)) {
*xmin = txmin;
if ((xmin) && (bounds.xmin < *xmin)) {
*xmin = bounds.xmin;
}
if ((xmax) && (txmax > *xmax)) {
*xmax = txmax;
if ((xmax) && (bounds.xmax > *xmax)) {
*xmax = bounds.xmax;
}
if ((ymin) && (tymin < *ymin)) {
*ymin = tymin;
if ((ymin) && (bounds.ymin < *ymin)) {
*ymin = bounds.ymin;
}
if ((ymax) && (tymax > *ymax)) {
*ymax = tymax;
if ((ymax) && (bounds.ymax > *ymax)) {
*ymax = bounds.ymax;
}
foundBounds = true;

View File

@ -600,7 +600,7 @@ static void rna_FCurve_group_set(PointerRNA *ptr,
/* calculate time extents of F-Curve */
static void rna_FCurve_range(FCurve *fcu, float range[2])
{
BKE_fcurve_calc_range(fcu, range, range + 1, false, false);
BKE_fcurve_calc_range(fcu, range, range + 1, false);
}
static bool rna_FCurve_is_empty_get(PointerRNA *ptr)