How Boolean Operations Work in Vector Graphics (Union, Intersection, Difference and XOR from scratch)
I spent a week implementing Union, Intersection, Difference, and XOR for my vector engine. The geometry turned out to be simpler than I expected. The hard part was one function — correctly labeling which side of a boundary each segment is on.
1. Find where the shapes intersect
2. Split both paths at intersection points
Now that we have the intersection points for both Path, we need to start splitting each by its segment and tracking the owner of each segment. We'll be using these segments for selection based on the operation.
3. Label each segment
So we have all the segments and their original path source, but we don't know if this segment is inside or outside the other shape. That's what this step is for. All we need to do is to pick a test point, and with the help of winding numbers we can check if it is inside or outside the other shape. The test point is obtained by taking the midpoint of each segment, offsetting it slightly along the normal direction, then casting a ray and counting how many times it crosses the path boundary. Odd crossings = inside. Even = outside.
4. Select segments based on the operation
Now we want to select each segment, but each operation has different rules. The table basically shows what segments are kept from each path based on the operation
| Operation | Keep from A | Keep from B |
|---|---|---|
| Union | outside B | outside A |
| Intersect | inside B | inside A |
| Diff A-B | outside B | inside A (reversed) |
| XOR | A-B + B-A |
For the XOR operation, the rule is basically the opposite of intersections, meaning segments in A that are not in B and segments in B that are not in A. If we take a look at these two statements, we basically get the Difference of A and B (A-B) and the Difference between B and A (B-A). I tried different approaches, but they weren't giving me the right shape, which led me to find this approach.
5. Reassemble into closed paths
The final step is a chain-following algorithm. Pick any unused segment and start a path. Find the next unused segment whose start point matches the current end point. Keep chaining until you return to the start — that closes one subpath. Any remaining unused segments form additional subpaths.
In all of these, the geometry is simple. The hard part is correctly labeling which side of each boundary you're on. The winding number does that in one check.