
On 12-10-2012 14:59, Jasper van de Gronde wrote:
On 2012-10-10 09:52, Tavmjong Bah wrote:
For my Cairo hack, I used the last three points (two handles, end point) of the cubic Bezier curve to determine the curvature. If the last two points are degenerate, I uses the start point, first handle, and end point. This seems to work fine (and as Cairo only uses cubic Bezier and lines at the rendering point I don't have to worry about arcs, quadratic Beziers, etc.). The math for finding the curvature of a cubic Bezier can be found at: http://tavmjong.free.fr/SVG/LINEJOIN/index.html
Nice exposition! The cross product is ill-defined in 2D though, so what you can do is take the determinant of the matrix with row (or column vectors) corresponding to v and v'. Alternatively, you can extend the vectors to be 3D of course, but that would essentially just be a roundabout way to compute a determinant of a 2x2 matrix.
To deal with zero-length handles, the easiest (and correct) method is probably to specify that the curvature is zero at the endpoint with the zero-length handle (if both handles are zero-length, then the Bézier curve forms a line segment). That this is correct can be derived from the fact that the unit tangent vector at the endpoint with a zero-length handle points in the same direction as the second derivative (so locally it's a straight line). This is a consequence of l'Hôpital's rule btw.
I don't think this is correct. It is possible to make segments with and without a zero-length handle that look exactly the same. Also, as you are moving the handle closer and closer to the knot, it does not converge to zero curvature, in fact it tends to lead to higher curvature instead of lower curvature. Wouldn't l'Hopital imply that you can get away by using +1 higher derivs instead?
If the first handle is zero-length and the second handle corresponds to the starting point, then again the whole curve is straight, and the curvature should be zero everywhere.
One way of doing this "automatically", is to simply add a small epsilon to the denominator (or clamp it to some small positive value). (Perhaps a bit obvious, but it's nice to know that it actually "does the right thing".)
... I noticed in your code that if the two circles don't intersect, you fall back to bevel join. I am thinking in the SVG 2 spec about falling back to miter join.
It might make more sense to use (something like) miterlimit, in some cases at least. Essentially there are two cases, either one circle is inside the other, or not. If they are "inside" each other, it might simply make perfect sense to just fill the area between the two circles, unless the area gets (really) large, then you'd probably want to limit it (using something akin to miterlimit). If the circles are not inside each other, the area "between" is always infinite (the entire plane minus the inside of the two circles). In that case, you really have to limit the extent of the join. But even then, it should be possible to still show part of the "true" join.
One possible way to specify the restriction for this kind of join would be to limit the arc-length on either side of the join. The main problem is then joining the end points, as a line segment between the two is not guaranteed to not intersect the circles. Some experimentation suggests that it might be possible to define a sensible boundary using interpolation between the two end points that is guaranteed not to intersect either of the circles.
I have no clue what you are saying here. :-)
I am starting to strongly dislike the miter fallback btw, simply because of it not being clear whether you have arc extension or a miter. Bevel fallback has a much better UI experience for me.
Cheers, Johan