For a personal project, I\'d need to find out if two cubic Bézier curves intersect. I don\'t need to know where: I just need to know if they do. However, I\'d need to do it
I would say that the easiest and likely fastest answer is to subdivide them into very small lines and find the points where the curves intersect, if they actually do.
public static void towardsCubic(double[] xy, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double t) {
double x, y;
x = (1 - t) * (1 - t) * (1 - t) * x0 + 3 * (1 - t) * (1 - t) * t * x1 + 3 * (1 - t) * t * t * x2 + t * t * t * x3;
y = (1 - t) * (1 - t) * (1 - t) * y0 + 3 * (1 - t) * (1 - t) * t * y1 + 3 * (1 - t) * t * t * y2 + t * t * t * y3;
xy[0] = x;
xy[1] = y;
}
public static void towardsQuad(double[] xy, double x0, double y0, double x1, double y1, double x2, double y2, double t) {
double x, y;
x = (1 - t) * (1 - t) * x0 + 2 * (1 - t) * t * x1 + t * t * x2;
y = (1 - t) * (1 - t) * y0 + 2 * (1 - t) * t * y1 + t * t * y2;
xy[0] = x;
xy[1] = y;
}
Obviously the brute force answer is bad See bo{4}'s answer, and there's a lot of linear geometry and collision detection that will actually help quite a lot.
Pick the number of slices you want for the curves. Something like 100 should be great.
We take all the segments and sort them based on the largest value of y they have. We then, add a second flag in the list for the smallest value of y for that segment.
We keep a list of active edges.
We iterate through the y-sorted list of segment, when we encounter a leading segment we add it to the active list. When we hit the small-y flagged value, we remove that segment from the active list.
We then can simply iterate through the entire set of segments with what amounts to a scan line, increasing our y monotonically as we simply iterate the list. We iterate through the values in our sorted list, which will typically remove one segment and add a new segment (or for split and merge nodes, add two segments or remove two segments). Thereby keeping an active list of relevant segments.
We run the fast fail intersect check as we add a new active segment to the list of active segments, against only that segment and and the currently active segments.
So at all times we know exactly which line segments are relevant, as we iterate through the sampled segments of our curves. We know these segments share overlap in the y-coords. We can fast fail any new segment that does not overlap with regard to its x-coords. In the rare case that they overlap in the x-coords, we then check if these segments intersect.
This will likely reduce the number of line intersection checks to basically the number of intersections.
foreach(segment in sortedSegmentList) {
if (segment.isLeading()) {
checkAgainstActives(segment);
actives.add(segment);
}
else actives.remove(segment)
}
checkAgainstActive() would simply check if this segment and any segment in the active list overlap their x-coords, if they do, then run the line intersect check on them, and take the appropriate action.
Also note, this will work for any number of curves, any type of curves, any order of curves, in any mixture. And if we iterate through the entire list of segments it will find every intersection. It will find every point a Bezier intersects a circle or every intersection that a dozen quadratic Bezier curves have with each other (or themselves), and all in the same split second.
The oft cited Chapter 7 document notes, for the subdivision algorithm:
"Once a pair of curves has been subdivided enough that they can each be approximated by a line segment to within a tolerance"
We can literally just skip the middleman. We can do this fast enough so to simply compare line segments with a tolerable amount of error from the curve. In the end, that's what the typical answer does.
Secondly, note, that the bulk of the speed increase for the collision detection here, namely the ordered list of segments sorted based on their highest y to add to the active list, and lowest y to remove from the active list, can likewise be done for the hull polygons of the Bezier curve directly. Our line segment is simply a polygon of order 2, but we could do any number of points just as trivially, and checking the bounding box of all the control points in whatever order of curve we're dealing with. So rather than a list of active segments, we would have a list of active hull polygons points. We could simply use De Casteljau's algorithm to split the curves, thereby sampling as these as subdivided curves rather than line segments. So rather than 2 points we'd have 4 or 7 or whatnot, and run the same routine being quite apt towards fast failing.
Adding the relevant group of points at max y, removing it at min y, and comparing only the active list. We can thus quickly and better implement the Bezier subdivision algorithm. By simply finding bounding box overlap, and then subdividing those curves which overlapped, and removing those that did not. As, again, we can do any number of curves, even ones subdivided from curves in the previous iteration. And like our segment approximation quickly solve for every intersection position between hundreds of different curves (even of different orders) very quickly. Just check all curves to see if the bounding boxes overlap, if they do, we subdivide those, until our curves are small enough or we ran out of them.