-
Notifications
You must be signed in to change notification settings - Fork 145
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
[WIP] Cubic bezier min bounding rect #97
[WIP] Cubic bezier min bounding rect #97
Conversation
Ooops! There's been some confusion about the inflection point indeed: Inflection points are points where the curve goes from turning in a direction to turning in another direction (my mental model of it is to imagine a bike riding along a curve and at some point the handlebars go from pointing to the left to pointing to the right). Good thing you are principled about unit testing and caught that! |
To calculate the x-inflection points we need to solve the equation of the derivative |
Thanks a lot for your comment, I'll try to make the required changes asap ! Btw this probably means I'll have to do a followup on the Quadratic curves too isn't it ? |
Cool, thanks a lot for looking into it! |
Yeah indeed, I noticed my mistake as I just went through the code again, thanks for the hint ! :) |
Ok I think I've found a way to solve this on paper, I'm gonna try to solve it in rust :) |
5bcec7b
to
42f3b64
Compare
Hey @nical , my find_y_extremums unit test is passing, but I don't understand if it should, and why it does, mind giving me a hint on how I'm supposed to proceed plz ? thanks a lot ^^' |
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 logic in find_y_extremum
looks good although it should be renamed into find_x_inflections
, and an equivalent method is needed for y. find_x/t_minimum/maximum
need to be fixed up so that they use this instead of the general inflection points, and then things should be good!
bezier/src/cubic_bezier.rs
Outdated
/// Return local y extremum points or None if this curve is monotone. | ||
/// | ||
/// This returns the advancements along the curve, not the actual y position. | ||
pub fn find_y_extremums(&self) -> UpToTwo<f32> { |
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.
In order to have a coherent API with the quadratic bézier equivalent, this should be called find_y_inflections
We also need an equivalent method for x.
pub fn find_x_minimum(&self) -> f32 { | ||
let mut min_t = 0.0; | ||
let mut min_x = self.from.x; | ||
if self.to.x < min_x { | ||
min_t = 1.0; | ||
min_x = self.to.x; | ||
} | ||
for t in self.find_inflection_points() { |
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.
You should keep this loop, but replace find_inflection_points
with find_x_inflections
. Otherwise, if the control point is left of the start and end points (in which case the curve will go more to the left of from and to), this would not return the correct result.
find_x_maximum
and find_y_minimum/maximum
also need to be fixed up to use find_x/y_inflections
instead of find_inflection_points`.
bezier/src/cubic_bezier.rs
Outdated
let min_x = self.sample(self.find_x_minimum()).x; | ||
let max_x = self.sample(self.find_x_maximum()).x; | ||
let min_y = self.sample(self.find_y_minimum()).y; | ||
let max_y = self.sample(self.find_y_maximum()).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.
A little detail, but you can write self.sample(self.find_x_minimum()).x
into self.sample_x(self.find_x_minimum());
to avoid computing y (not extremely important since the code is already correct).
bezier/src/cubic_bezier.rs
Outdated
ctrl2: ctrl3a, | ||
to: to, | ||
}); | ||
CubicBezierSegment { |
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.
nit: Not sure why this is getting an extra indentation level.
bezier/src/cubic_bezier.rs
Outdated
let mut ret = UpToTwo::new(); | ||
// See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation | ||
// The derivative of a cubic bezier curve is a curve representing a second degree polynomial function | ||
// f(x) = a * x² + b * x + c such as : |
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.
To avoid confusion, let's say that it is the derivative of the curve projected on the y axis, and use y
as the variable in the formula: f(y) = a * y² + b * y + c
(because we also need the equivalent for the x axis).
bezier/src/cubic_bezier.rs
Outdated
}; | ||
|
||
let mut expected_y_extremums = UpToTwo::new(); | ||
expected_y_extremums.push(0.5); |
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 don't think that 0.5 is an y-inflection
point of the curve (although it is an inflection point). After drawing it on a piece of paper it looks like this curve is monotone (always increasing) along y.
I think that this curve should have no y-inflections
(and also no x-inflections
), even if, confusingly, 0.5 is a "normal" inflection point.
bezier/src/cubic_bezier.rs
Outdated
let discriminant_sqrt = discriminant.sqrt(); | ||
println!("delta_sqrt : {} ", discriminant_sqrt); | ||
|
||
let first_extremum = (-b - discriminant_sqrt) / 2.0 * a; |
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.
It's easy to get confused about the precedence of operators. In order to avoid ambiguity, let's put some parentheses around 2.0 * a
here and below.
Thanks a lot for taking time to review my code, I'll edit it tomorrow :) |
I am struggling to explain the distinction of x-inflection/y-inflection, and general inflection points of the curve without making it even more confusing, don't hesitate to ask for more details and/or help! |
I'll give it a shot and let you know. Again thanks a lot for your patience ! :) |
I fixed a couple of things, I guess I'm like halfway there. About the naming convention I'm acutally not looking for inflexion points (which are points where the Second derivative equals zero), but local extremums (which are points when whe derivative equals zero), so I'm not really sure we want to rename it, do we ? |
That's the distinction between inflection point of the parametric curve (second dervative), and x-inflection (where the curve x(t) or x(y) has an inflection which corresponds to first derivative). |
Oh I think I understand now ! Well it's up to you, I believe both should have different names, but I'm not sure x/y_extremums is the most obvious name. Do you have any suggestions? Else I will rename it to x/y_inflection, and hope no confusion will be made :) |
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.
Getting there, you still need to make find_x_minimum
and find_y_minimum
use the function you added (whatever the name we end up picking for it).
On the topic of naming, if we are going for the extremum terminology, let's call the functions find_local_x_extrema
since it does not find the global extrema of the curve (which are often the end points), and the plural of this word is with an 'a' according to wikipedia (and some distant latin classes in which I didn't pay enough attention).
There are a few other review comments to fix and after that, you can remove the printfs and rebase on top of master and it should be ready to merge!
bezier/src/cubic_bezier.rs
Outdated
return ret; | ||
} | ||
|
||
fn in_range(t: f32) -> bool { t >= 0.0 && t < 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.
Let's use t strictly superior to zero here, since this is primarily useful to split the curve into monotone segments and computing the bounding rectangle of the curve, and in both situations there is no need to special-case of split the curve at t = 0.0.
bezier/src/cubic_bezier.rs
Outdated
/// Return local y extremum points or None if this curve is monotone. | ||
/// | ||
/// This returns the advancements along the curve, not the actual y position. | ||
pub fn find_y_extremums(&self) -> UpToTwo<f32> { |
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.
In order to avoid duplicating this code, you could transpose the curve (flip x and y of all its points), and call find_x_extremums, like solve_t_for_y does.
bezier/src/cubic_bezier.rs
Outdated
@@ -183,6 +183,140 @@ impl CubicBezierSegment { | |||
find_cubic_bezier_inflection_points(self) | |||
} | |||
|
|||
/// Return local x extremum points or None if this curve is monotone. |
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.
nit: indentation looks like it's off by one level.
885e814
to
8e3b7f5
Compare
This seems fine, although I don't feel comfortable with the magic numbers I've just added to the minimum_bounding_rect_for_cubic_bezier_segment test, what do you think ?
|
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.
Looks great! Let's fuzz that test a bit and then the PR will be ready for merge. Thanks and kudos for your determination!
bezier/src/cubic_bezier.rs
Outdated
to: Point::new(2.0, 0.0), | ||
}; | ||
|
||
let expected_minimum_bounding_rect = rect(0.0, -0.57735026, 2.0, 1.1547005); |
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 prefer to have a fuzzier test: something like checking that rect(0.0, -0.6, 2.0, 1.2).contains_rect(&actual_minimum_bounding_rect)
. That way we should catch most regressions while not break the test every time we change the order of some operations and end up with slightly different rounding errors.
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.
Maybe also have a lower bound like also checking that actual_minimum_bounding_rect.contains_rect(&rect(0.0, 0.5, 2.0, 1.0))
in addition to the other contains_rect test.
… finding local x and y extrema
9523847
to
eca01d2
Compare
The test is fuzzed now ! :) plus I've squashed my commits :D Thanks a lot for your patience, I hope I'll be able to tackle harder features, feel free to ping me if you think an unassigned feature you have would match my skills and help me improve! 🙌 |
\o/ @o0Ignition0o If you are up for a bit more challenge (but reasonable still), I think that adding features to the stroke tessellator is fun to tackle because you get to actually render something and see the feature with your own eyes which is quite rewarding and the stroke tessellator is still reasonably understandable (while the fill tessellator is a really complex algorithm). For example #34 and #35 would be fun to implement. As always, don't hesitate to ask for help! |
Great, will try it then :D Oh and by the way the quadratic and cubic intersection features should be easy now that these methods are available ! |
Went with an implementation so far but my unit test is not passing. I think I don't correctly understand what an inflection point is. Is my setup phase incorrect or is it the way I implement the method?