Fix #110169: nearly flat normalized fcurves jump around in editor #110796
|
@ -170,6 +170,26 @@ MINLINE size_t clamp_z(size_t value, size_t min, size_t max);
|
|||
* \param max_diff: the maximum absolute difference.
|
||||
*/
|
||||
MINLINE int compare_ff(float a, float b, float max_diff);
|
||||
/**
|
||||
* Computes the distance between two floats in ulps.
|
||||
*
|
||||
* In other words, returns zero if the floats are exactly equal, and
|
||||
* otherwise returns 1 plus the number of (unique) representable floats
|
||||
* between `a` and `b` on the number line.
|
||||
*
|
||||
* Notes:
|
||||
* - The order of `a` and `b` doesn't matter. The returned value is the
|
||||
* absolute difference.
|
||||
* - Unlike many ulp difference functions, this function handles the
|
||||
* difference between positive and negative floats in a meaningful way.
|
||||
* It returns the number (plus 1) of representable floats between those
|
||||
* two values as they would be arranged on a number line.
|
||||
* - Zero and negative zero are *not* considered unique from each other.
|
||||
* They are counted together as a single float in the difference.
|
||||
* - NaNs are not handled meaningfully. If either number is NaN, this
|
||||
* function returns uint max (0xffffffff).
|
||||
*/
|
||||
MINLINE uint ulp_diff_ff(float a, float b);
|
||||
/**
|
||||
* Almost-equal for IEEE floats, using their integer representation
|
||||
* (mixing ULP and absolute difference methods).
|
||||
|
|
|
@ -644,26 +644,48 @@ MINLINE int compare_ff(float a, float b, const float max_diff)
|
|||
return fabsf(a - b) <= max_diff;
|
||||
}
|
||||
|
||||
MINLINE int compare_ff_relative(float a, float b, const float max_diff, const int max_ulps)
|
||||
MINLINE uint ulp_diff_ff(float a, float b)
|
||||
{
|
||||
BLI_assert(sizeof(float) == sizeof(uint));
|
||||
|
||||
const uint sign_bit = 0x80000000;
|
||||
const uint infinity = 0x7f800000;
|
||||
|
||||
union {
|
||||
float f;
|
||||
int i;
|
||||
uint i;
|
||||
} ua, ub;
|
||||
ua.f = a;
|
||||
ub.f = b;
|
||||
|
||||
BLI_assert(sizeof(float) == sizeof(int));
|
||||
BLI_assert(max_ulps < (1 << 22));
|
||||
const uint a_sign = ua.i & sign_bit;
|
||||
const uint b_sign = ub.i & sign_bit;
|
||||
const uint a_abs = ua.i & ~sign_bit;
|
||||
const uint b_abs = ub.i & ~sign_bit;
|
||||
|
||||
if (a_abs > infinity || b_abs > infinity) {
|
||||
/* NaNs always return maximum ulps apart. */
|
||||
return 0xffffffff;
|
||||
}
|
||||
else if (a_sign == b_sign) {
|
||||
const uint min_abs = a_abs < b_abs ? a_abs : b_abs;
|
||||
const uint max_abs = a_abs > b_abs ? a_abs : b_abs;
|
||||
return max_abs - min_abs;
|
||||
}
|
||||
else {
|
||||
return a_abs + b_abs;
|
||||
}
|
||||
}
|
||||
|
||||
MINLINE int compare_ff_relative(float a, float b, const float max_diff, const int max_ulps)
|
||||
{
|
||||
BLI_assert(max_ulps >= 0 && max_ulps < (1 << 22));
|
||||
|
||||
if (fabsf(a - b) <= max_diff) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ua.f = a;
|
||||
ub.f = b;
|
||||
|
||||
/* Important to compare sign from integers, since (-0.0f < 0) is false
|
||||
* (though this shall not be an issue in common cases)... */
|
||||
return ((ua.i < 0) != (ub.i < 0)) ? 0 : (abs(ua.i - ub.i) <= max_ulps) ? 1 : 0;
|
||||
return (ulp_diff_ff(a, b) <= (uint)max_ulps) ? 1 : 0;
|
||||
}
|
||||
|
||||
MINLINE bool compare_threshold_relative(const float value1, const float value2, const float thresh)
|
||||
|
|
|
@ -62,32 +62,57 @@ TEST(math_base, CompareFFRelativeZero)
|
|||
|
||||
EXPECT_TRUE(compare_ff_relative(f0, f1, -1.0f, 3));
|
||||
EXPECT_TRUE(compare_ff_relative(f1, f0, -1.0f, 3));
|
||||
EXPECT_FALSE(compare_ff_relative(f0, f1, -1.0f, 2));
|
||||
EXPECT_FALSE(compare_ff_relative(f1, f0, -1.0f, 2));
|
||||
EXPECT_TRUE(compare_ff_relative(f0, f1, max_diff, 2));
|
||||
EXPECT_TRUE(compare_ff_relative(f1, f0, max_diff, 2));
|
||||
|
||||
EXPECT_FALSE(compare_ff_relative(f0, f1, -1.0f, 1));
|
||||
EXPECT_FALSE(compare_ff_relative(f1, f0, -1.0f, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, f1, -1.0f, 3));
|
||||
EXPECT_TRUE(compare_ff_relative(f1, fn0, -1.0f, 3));
|
||||
EXPECT_FALSE(compare_ff_relative(fn0, f1, -1.0f, 2));
|
||||
EXPECT_FALSE(compare_ff_relative(f1, fn0, -1.0f, 2));
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, f1, max_diff, 2));
|
||||
EXPECT_TRUE(compare_ff_relative(f1, fn0, max_diff, 2));
|
||||
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, fn1, -1.0f, 8));
|
||||
EXPECT_TRUE(compare_ff_relative(fn1, fn0, -1.0f, 8));
|
||||
|
||||
EXPECT_TRUE(compare_ff_relative(f0, f1, max_diff, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(f1, f0, max_diff, 1));
|
||||
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, f0, max_diff, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(f0, fn0, max_diff, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, fn1, -1.0f, 2));
|
||||
EXPECT_TRUE(compare_ff_relative(fn1, fn0, -1.0f, 2));
|
||||
EXPECT_FALSE(compare_ff_relative(fn0, fn1, -1.0f, 1));
|
||||
EXPECT_FALSE(compare_ff_relative(fn1, fn0, -1.0f, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, fn1, max_diff, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(fn1, fn0, max_diff, 1));
|
||||
|
||||
EXPECT_TRUE(compare_ff_relative(f0, fn1, -1.0f, 2));
|
||||
EXPECT_TRUE(compare_ff_relative(fn1, f0, -1.0f, 2));
|
||||
EXPECT_FALSE(compare_ff_relative(f0, fn1, -1.0f, 1));
|
||||
EXPECT_FALSE(compare_ff_relative(fn1, f0, -1.0f, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(f0, fn1, max_diff, 1));
|
||||
EXPECT_TRUE(compare_ff_relative(fn1, f0, max_diff, 1));
|
||||
|
||||
/* NOTE: in theory, this should return false, since 0.0f and -0.0f have 0x80000000 diff,
|
||||
* but overflow in subtraction seems to break something here
|
||||
* (abs(*(int *)&fn0 - *(int *)&f0) == 0x80000000 == fn0), probably because int32 cannot
|
||||
* hold this abs value. this is yet another illustration of why one shall never use (near-)zero
|
||||
* floats in pure-ULP comparison. */
|
||||
// EXPECT_FALSE(compare_ff_relative(fn0, f0, -1.0f, 1024));
|
||||
// EXPECT_FALSE(compare_ff_relative(f0, fn0, -1.0f, 1024));
|
||||
EXPECT_TRUE(compare_ff_relative(fn0, f0, -1.0f, 0));
|
||||
EXPECT_TRUE(compare_ff_relative(f0, fn0, -1.0f, 0));
|
||||
}
|
||||
|
||||
EXPECT_FALSE(compare_ff_relative(fn0, f1, -1.0f, 1024));
|
||||
EXPECT_FALSE(compare_ff_relative(f1, fn0, -1.0f, 1024));
|
||||
TEST(math_base, UlpDiffFF)
|
||||
{
|
||||
EXPECT_EQ(ulp_diff_ff(0.0, 0.0), 0);
|
||||
EXPECT_EQ(ulp_diff_ff(0.0, -0.0), 0);
|
||||
EXPECT_EQ(ulp_diff_ff(-0.0, -0.0), 0);
|
||||
EXPECT_EQ(ulp_diff_ff(1.0, 1.0), 0);
|
||||
EXPECT_EQ(ulp_diff_ff(1.0, 2.0), 1 << 23);
|
||||
EXPECT_EQ(ulp_diff_ff(2.0, 4.0), 1 << 23);
|
||||
EXPECT_EQ(ulp_diff_ff(-1.0, -2.0), 1 << 23);
|
||||
EXPECT_EQ(ulp_diff_ff(-2.0, -4.0), 1 << 23);
|
||||
EXPECT_EQ(ulp_diff_ff(-1.0, 1.0), 0x7f000000);
|
||||
EXPECT_EQ(ulp_diff_ff(0.0, 1.0), 0x3f800000);
|
||||
EXPECT_EQ(ulp_diff_ff(-0.0, 1.0), 0x3f800000);
|
||||
EXPECT_EQ(ulp_diff_ff(0.0, -1.0), 0x3f800000);
|
||||
EXPECT_EQ(ulp_diff_ff(-0.0, -1.0), 0x3f800000);
|
||||
EXPECT_EQ(ulp_diff_ff(INFINITY, -INFINITY), 0xff000000);
|
||||
EXPECT_EQ(ulp_diff_ff(NAN, NAN), 0xffffffff);
|
||||
EXPECT_EQ(ulp_diff_ff(NAN, 1.0), 0xffffffff);
|
||||
EXPECT_EQ(ulp_diff_ff(1.0, NAN), 0xffffffff);
|
||||
EXPECT_EQ(ulp_diff_ff(-NAN, 1.0), 0xffffffff);
|
||||
EXPECT_EQ(ulp_diff_ff(1.0, -NAN), 0xffffffff);
|
||||
}
|
||||
|
||||
TEST(math_base, Log2FloorU)
|
||||
|
|
|
@ -504,14 +504,19 @@ static float normalization_factor_get(Scene *scene, FCurve *fcu, short flag, flo
|
|||
float min_coord = FLT_MAX;
|
||||
fcurve_scene_coord_range_get(scene, fcu, &min_coord, &max_coord);
|
||||
|
||||
if (max_coord > min_coord) {
|
||||
/* We use an ulps-based floating point comparison here, with the
|
||||
* rationale that if there are too few possible values between
|
||||
* `min_coord` and `max_coord`, then after display normalization it
|
||||
* will certainly be a weird quantized experience for the user anyway.
|
||||
*/
|
||||
if (min_coord < max_coord && ulp_diff_ff(min_coord, max_coord) > 256) {
|
||||
/* Normalize. */
|
||||
const float range = max_coord - min_coord;
|
||||
if (range > FLT_EPSILON) {
|
||||
factor = 2.0f / range;
|
||||
}
|
||||
factor = 2.0f / range;
|
||||
offset = -min_coord - range / 2.0f;
|
||||
}
|
||||
else if (max_coord == min_coord) {
|
||||
else {
|
||||
/* Skip normalization. */
|
||||
factor = 1.0f;
|
||||
offset = -min_coord;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue