Fix #119360: Precision issue with cycle modifier

The issue was a floating point precision issue
between the number of the cycle and the time
within the cycle (`cycle` and `cyct`).
Due to that the `cycle` would advance to the
next whole number while the `cycle time`
is still slightly under that.

This is fixed by calculating the `cycle` with double precision.

This has a measurable performance
impact (for `fcm_cycles_time`)

| Before | After |
| - | - |
| 39ns | 44ns |

For fcurves with a cycle modifier,
this code is run at every evaluate call.
The only time when that would be an issue is
in the graph editor, where there is an evaluate
call for roughly every pixel for curves that have a modifier.
However even in that scenario the performance
is the same within run to run variance (for `graph_draw_curves`)

| Before | After |
| - | - |
| 91565ns | 91430ns |

Pull Request: https://projects.blender.org/blender/blender/pulls/123222
This commit is contained in:
Christoph Lendenfeld 2024-06-14 15:43:34 +02:00 committed by Christoph Lendenfeld
parent 6c29892909
commit 7e9b580546

@ -624,7 +624,7 @@ static float fcm_cycles_time(
{
const FMod_Cycles *data = (FMod_Cycles *)fcm->data;
tFCMED_Cycles *storage = static_cast<tFCMED_Cycles *>(storage_);
float prevkey[2], lastkey[2], cycyofs = 0.0f;
float firstkey[2], lastkey[2], cycyofs = 0.0f;
short side = 0, mode = 0;
int cycles = 0;
float ofs = 0;
@ -642,11 +642,11 @@ static float fcm_cycles_time(
/* calculate new evaltime due to cyclic interpolation */
if (fcu->bezt) {
const BezTriple *prevbezt = fcu->bezt;
const BezTriple *lastbezt = prevbezt + fcu->totvert - 1;
const BezTriple *firstbezt = &fcu->bezt[0];
const BezTriple *lastbezt = &fcu->bezt[fcu->totvert - 1];
prevkey[0] = prevbezt->vec[1][0];
prevkey[1] = prevbezt->vec[1][1];
firstkey[0] = firstbezt->vec[1][0];
firstkey[1] = firstbezt->vec[1][1];
lastkey[0] = lastbezt->vec[1][0];
lastkey[1] = lastbezt->vec[1][1];
@ -656,8 +656,8 @@ static float fcm_cycles_time(
const FPoint *prevfpt = fcu->fpt;
const FPoint *lastfpt = prevfpt + fcu->totvert - 1;
prevkey[0] = prevfpt->vec[0];
prevkey[1] = prevfpt->vec[1];
firstkey[0] = prevfpt->vec[0];
firstkey[1] = prevfpt->vec[1];
lastkey[0] = lastfpt->vec[0];
lastkey[1] = lastfpt->vec[1];
@ -667,12 +667,12 @@ static float fcm_cycles_time(
* 1) if in data range, definitely don't do anything
* 2) if before first frame or after last frame, make sure some cycling is in use
*/
if (evaltime < prevkey[0]) {
if (evaltime < firstkey[0]) {
if (data->before_mode) {
side = -1;
mode = data->before_mode;
cycles = data->before_cycles;
ofs = prevkey[0];
ofs = firstkey[0];
}
}
else if (evaltime > lastkey[0]) {
@ -690,17 +690,18 @@ static float fcm_cycles_time(
/* find relative place within a cycle */
{
/* calculate period and amplitude (total height) of a cycle */
const float cycdx = lastkey[0] - prevkey[0];
const float cycdy = lastkey[1] - prevkey[1];
const float cycdx = lastkey[0] - firstkey[0];
const float cycdy = lastkey[1] - firstkey[1];
/* check if cycle is infinitely small, to be point of being impossible to use */
if (cycdx == 0) {
return evaltime;
}
/* calculate the 'number' of the cycle */
const float cycle = (float(side) * (evaltime - ofs) / cycdx);
/* Calculate the 'number' of the cycle. Needs to be a double to combat precision issues like
* #119360. With floats it can happen that the `cycle` jumps to the next full number, while
* `cyct` below is still behind. */
const double cycle = side * (double(evaltime) - double(ofs)) / double(cycdx);
/* calculate the time inside the cycle */
const float cyct = fmod(evaltime - ofs, cycdx);
@ -730,10 +731,10 @@ static float fcm_cycles_time(
/* special case for cycle start/end */
if (cyct == 0.0f) {
evaltime = (side == 1 ? lastkey[0] : prevkey[0]);
evaltime = (side == 1 ? lastkey[0] : firstkey[0]);
if ((mode == FCM_EXTRAPOLATE_MIRROR) && (int(cycle) % 2)) {
evaltime = (side == 1 ? prevkey[0] : lastkey[0]);
evaltime = (side == 1 ? firstkey[0] : lastkey[0]);
}
}
/* calculate where in the cycle we are (overwrite evaltime to reflect this) */
@ -744,7 +745,7 @@ static float fcm_cycles_time(
* (result of fmod will be negative, and with different phase).
*/
if (side < 0) {
evaltime = prevkey[0] - cyct;
evaltime = firstkey[0] - cyct;
}
else {
evaltime = lastkey[0] - cyct;
@ -752,9 +753,9 @@ static float fcm_cycles_time(
}
else {
/* the cycle is played normally... */
evaltime = prevkey[0] + cyct;
evaltime = firstkey[0] + cyct;
}
if (evaltime < prevkey[0]) {
if (evaltime < firstkey[0]) {
evaltime += cycdx;
}
}