-
Notifications
You must be signed in to change notification settings - Fork 9
Add cubic bezier easing #273
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
92affc3 to
cd8b6fb
Compare
| * | ||
| * See: https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui-util/src/commonMain/kotlin/androidx/compose/ui/util/MathHelpers.kt;l=160 | ||
| */ | ||
| static float fast_cbrt(float x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use the native cbrtf? (you can also drop the android copyright then)
libnopegl/src/node_animkeyframe.c
Outdated
| if (double_close_to(d, 0.0, FLT_EPSILON)) { | ||
| if (double_close_to(a, 0.0, FLT_EPSILON)) { | ||
| if (double_close_to(b, 0.0, FLT_EPSILON)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to prevent a division by zero you can check directly against 0.0, it is enough to cover all NaN cases (assuming the numerator is finite). So I'm assuming this check is to prevent an Inf value (caused by overflows). Unfortunately you can't check for it like that. For example 3e-16 will pass the epsilon check, but 1e+308/3e-16 will make an Inf.
Note
You probably wanted to use DBL_EPSILON here since you're working with double precision. But in general, epsilon is never a good threshold against NaN/Inf (and actually using double epsilon here will make it worse).
libnopegl/src/node_animkeyframe.c
Outdated
|
|
||
| static int double_close_to(double value, double ref, double eps) | ||
| { | ||
| return fabs(value - ref) < eps; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref and eps are always the same in your calls, so it could probably be called close_to_zero and take only one parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
libnopegl/src/node_animkeyframe.c
Outdated
| const double da = 3.0f * (b - a); | ||
| const double db = 3.0f * (c - b); | ||
| const double dc = 3.0f * (d - c); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why use f suffix if you're working with doubles?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
| static float clamp_root(float r) | ||
| { | ||
| const float s = NGLI_CLAMP(r, 0.f, 1.f); | ||
| return fabsf(s - r) > FLT_EPSILON ? NAN : s; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FLT_EPSILON is, as far as I can tell, not a very good threshold choice as it's probably going to filter out all sorts of cases where the root is close to 0 or 1: it's a very restrictive value, and at the same time not 100%. A more arbitrary 1e-6 (that can be understood as a "precision" rounding) will likely do a better job.
| double a = 3.0 * (p0 - 2.0 * p1 + p2); | ||
| double b = 3.0 * (p1 - p0); | ||
| double c = p0; | ||
| double d = -p0 + 3.0 * (p1 - p2) + p3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I'm assuming this is the cubic points being converted to polynomial coefficients, but the value are weirdly shuffled. You made your polynomial a, b and c.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I implemented it so it matches the ref mentioned in the comment, see: https://pomax.github.io/bezierinfo/#yforx
| double q = sqrt(b * b - 4.0 * a * c); | ||
| double a2 = 2.0 * a; | ||
|
|
||
| float root = clamp_root(((float) ((q - b) / a2))); | ||
| if (!isnan(root)) | ||
| return root; | ||
|
|
||
| return clamp_root(((float) ((-b - q) / a2))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: there is a simpler quadratic formula which we're using in distmap where you find a middle point
Since there are less subtractions, I also believe it leads to a lower risk of catastrophic cancellation (to be verified though).
| } | ||
| return clamp_root((float) (-c / b)); | ||
| } else { | ||
| double q = sqrt(b * b - 4.0 * a * c); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is missing a check if the
| double u1 = fast_cbrt((float)(-q2 + sd)); | ||
| double v1 = fast_cbrt((float)(q2 + sd)); | ||
|
|
||
| return clamp_root((float)(u1 - v1 - a3)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that in my experience the analytic cubic is filled with numerical instability and I wouldn't recommend it... I ended up dropping it from the distmap for a more stable iterative algorithm.
| double a = 1.0 / 3.0 + (p1 - p2); | ||
| double b = (p2 - 2.0 * p1); | ||
| double c = p1; | ||
| return 3.0 * ((a * t + b) * t + c) * t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend computing and caching the polynomial for faster evaluation.
| const easing_type a = PARAM(0, 0.0); | ||
| const easing_type b = PARAM(1, 0.0); | ||
| const easing_type c = PARAM(2, 1.0); | ||
| const easing_type d = PARAM(3, 1.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here is something: with easing, the starting and ending points are always respectively (0,0) and (1,1). If you need some sort of offsetting, you can use easing_start_offset and easing_end_offset.
Next, you can't have the curve going backward, which means
All these constraints may also make the solver and friends way more convenient and may avoid a bunch of tests. You also won't need to extend the number of parameter from 2 to 4.
| const easing_type c = PARAM(2, 1.0); | ||
| const easing_type d = PARAM(3, 1.0); | ||
|
|
||
| const easing_type v = NGLI_MAX(t, FLT_EPSILON); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is
libnopegl/src/node_animkeyframe.c
Outdated
| return 1.0; | ||
| } | ||
|
|
||
| return evaluate_cubic(db, 0.f, r); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
evaluate_cubic() takes double arguments so 0.f should be 0.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
This is needed to support easings based on cubic Bézier curves.
2ea27d9 to
6f59a2f
Compare
6f59a2f to
8749f01
Compare
No description provided.