-
-
Notifications
You must be signed in to change notification settings - Fork 21.3k
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
Fix for "off-by-1" bug when sampling "baked" curve data towards the end of the curve. #76617
Conversation
Hi @tektrip-biggles! Can you create an issue about the baked curve issue? It's preferred for PRs to have linked issues. So we can know which issues have been created and which ones are fixed. It will help to see if your PR fixes the issue too, as a MRP (a minimal reproduction project) is asked on creation. Thank you for your first PR, by the way! |
I personally feel like for a bug as small as this one (2 line changes and a pretty believable explanation), maybe creating an associated issue might not be necessary :) Also, notice that the tests are failing, which is to be expected if the values were generated using the previous (potentially buggy) implementation. Would you be able to check those tests that are failing and use them to show that your implementation is correct? The PR would need to change those tests to the proper numbers, if it turns out that the tests are indeed "buggy" :) |
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.
Checked the code, LGTM. I've added some comments for potential additional changes, they should't affect the behavior though (so they're not necessary).
As you can see tests fail after the changes, they need to be updated (tests/scene/test_curve.h
). Would make sense to add test case baked-sampling at (bake_resolution - 0.5) / bake_resolution
which shouldn't produce the same result as sampling at 1.0
. Such test case would fail before/pass after this PR.
@@ -489,7 +489,7 @@ real_t Curve::sample_baked(real_t p_offset) const { | |||
} | |||
|
|||
// Get interpolation index | |||
real_t fi = p_offset * _baked_cache.size(); | |||
real_t fi = p_offset * (_baked_cache.size() - 1); |
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.
The code below could be simplified to:
int i = Math::floor(fi);
if (i < 0) {
return _baked_cache[0];
} else if (i + 1 < _baked_cache.size()) {
real_t t = fi - i;
return Math::lerp(_baked_cache[i], _baked_cache[i + 1], t);
} else {
return _baked_cache[_baked_cache.size() - 1];
}
But it should work correctly as is.
@@ -452,7 +452,7 @@ void Curve::bake() { | |||
_baked_cache.resize(_bake_resolution); | |||
|
|||
for (int i = 1; i < _bake_resolution - 1; ++i) { | |||
real_t x = i / static_cast<real_t>(_bake_resolution); | |||
real_t x = i / static_cast<real_t>(_bake_resolution - 1); | |||
real_t y = sample(x); | |||
_baked_cache.write[i] = y; |
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.
These _baked_cache.write[i]
(this one and the following ones) could be replaced by obtaining the pointer once before the loop like: real_t *baked_cache_ptr = _baked_cache.ptrw();
and doing _baked_cache_ptr[i]
, less potential overhead.
Hey, have created a MRP for others to toggle between Will see about fixing those tests as well... |
Ok, in terms of the tests, I've added the "sawtooth" ones that were being used to test into my MRP, along with some controls to make it easier to measure specific sample values using both MRP_Curve_Sample_Testing.mov(Note that the above is using the "fixed" code) It's interesting that with a bake resolution of 11, the new code results in getting the exact same sample/output values from the test offset/input values ( As a result I'm not sure if the current tests for As an aside, from the MRP tool & video above it's also quite easy to see why this issue was not spotted earlier: the "test curve" used already has a "flat" section where the offset approaches 1! |
Have filed a bug report as well (#76623 ) |
Just a reminder about this one! I'd be suuuper excited to see this bug fixed and to get a new contributor to Godot! I feel like the path to get this merged is to:
@adamscott this one feels like more of a bugfix than an enhancement, and can likely be cherry-picked for 4.0 too? |
Haha, smart fix for the test 🤣 It's actually a pretty good illustration of how things were wrong, right? Could we get CI going for this one @akien-mga, then maybe consider merging? |
Oh, also, please squash your commits into a single one :) It's part of the workflow! More info here :) |
Heya, @anvilfolk thanks for reminding me to get back to this! Apologies for the delay, a whole bunch of old project stuff popped up over the past few weeks. Didn't actually realise that each commit to my branch would instantly show up here! Will rebase / squash my changes. That's just a case of running Decided to fix some of the test messages that looked like they'd been copy/pasted without updating the mentioned offsets to match the values being tested. Also added a new test case following @kleonc 's suggestion & have just confirmed that this fails before/passes after the change I made previously. I wanted to keep my changes to the bare minimum though so didn't implement his other suggestions |
b8afe72
to
b32c4b0
Compare
Commits squashed as requested! |
Yay! Looks like a single tiny change needed and it's ready to rumble! Congrats on what will be your very first contribution! :D |
Now you gotta squash commits again 🤣 |
…he end of the curve. [Fixed] Failing test "[Curve] Custom curve with free tangents" by setting the curve's `bake_resolution` to 11. [Fixed] test messages in "[Curve] Custom curve with free tangents" to match sample offset used in each test [Added] New test "[Curve] Straight line offset test" in response to pull request feedback. Update tests/scene/test_curve.h Co-authored-by: kleonc <9283098+kleonc@users.noreply.github.com>
c605a12
to
c920a4f
Compare
Thanks! And congrats for your first merged Godot contribution 🎉 |
Yesssss, welcome to the club @tektrip-biggles! Big congratulations!!! :) If you're looking to hangout and contribute some more, don't forget there's the chat for godot contributors here :) |
Awesome! My first ever contribution to a major open source project! Thanks for all the support @anvilfolk & general help getting this merged, guys :-) |
I noticed that when using
Curve::sample()
&Curve::sample_baked()
, I was getting different results for the same curve, when sampling an offset close to 1.By setting a simple, rising curve's
bake_resolution
to a low value (e.g. 10) and plotting the sample values on a line, the error can be seen quite clearly.Using
Curve::sample()
, the curve appears as expected:Using
Curve::sample_baked()
for the same curve results in a strange "kink" followed by a horizontal, flat section towards the end (top right) of the curve:This was tracked down to a small math error in
Curve::bake()
. Note the original function below fromcurve.cpp
Since the first and last baked points will be set to be equal to the first and last points of the curve itself, the loop part is essentially used to "cut" the curve into$n-1$ equally sized intervals with $n-2$ values baked between each interval (resulting in $n$ baked points in total once the first & last points are also added). To illustrate the situation more concretely, the error occurs because if we are to create, say, 10 "baked points", that will actually result in only $9$ sections or "intervals" (and $8$ "middle" baked points) so the length of each section should be $\frac{1}{9}$ instead of $\frac{1}{10}$ .
In other words, this line was assuming the intervals between each baked point would be$\frac{1}{n}$ :
But if we have$n-1$ intervals, the length of each should in fact be $\frac{1}{n-1}$ , so the line just needs to be changed to:
This will ensure that each of the "middle" baked points is in fact equally spaced between the "start" and "end" baked points.
Similarly, a corresponding change needs to be made to
Curve::sample_baked()
, changing:real_t fi = p_offset * _baked_cache.size();
to:
Making the 2 small changes in this pull request results in
Curve::sample_baked()
returning the correct and expected valuesFixes #76623