Open elalish opened 2 years ago
Apart from Warp, Refine can also cause overlap when the surfaces are too close.
The tricky part to making this robust is these new triplet vert (the name I'll give to verts at the intersection of three tris) pair-ups. In the Boolean version, we know for each vert on a retained edge whether it is a start or an end vert, and this pre-arranged consistency is fundamentally where our manifoldness guarantee stems from. In the case of these triplet verts, it is not known for each new edge whether they are start or end, only that within each partially-retained face, the two edges linking to that vert must be opposite (one start and one end), while on a given new edge there must be an equal number of start and end verts.
This looks like a kind of binary integer programming problem, except all the constraints are equalities and there is no cost function - we only want an arbitrary valid solution. I'm not too concerned about NP-hardness, since in practice these knots of related constraints will be quite small and local. I'm guessing NP-hard means there is no clever way to solve this, so probably just need a little decision tree we can search exhaustively.
No, the problem may just be NP hard in general, but exists efficient algorithms for special cases. For a lot of NP hard graph problems, we can solve them efficiently for planar graphs or tree like graphs. You can have a look at parameterized algorithms if you are interested. One important property is whether or not the problem can be split into subproblems locally, and it seems that it is possible.
Well, and actually on further reflection, I believe this is in fact an XOR-SAT problem, which is P because it can be solved by Gaussian elimination. Not sure if that's the route we want to take, but it's nice that this isn't as hard.
Okay, upon further reflection, the above approach isn't going to work. While an XOR-SAT might fix issues akin to figure 6.5 from Julian Smith's paper, I can't see a straightforward way to deal with the more common problems shown in figure 6.4.
I'm now inspired by his approach to robustifying the Bently-Ottmann algorithm for 2D overlap removal, from Section 7.4 and after. Here, instead of using shadow functions and symbolic perturbation to make every comparison into a reliable boolean value, now there are three options: above
, below
, or on
. on
is any comparison that is not definitely above
or below
(more like is a vert on one side of a line or the other - I'm not trying to imply coordinate direction). For a vert on
an edge, that edge is split, so degeneracies are effectively snapped together. This has an extra benefit which is that it keeps degeneracies from entering the output in the first place (very much unlike the Boolean), which means we shouldn't have to spend as much time in our serial simplification post-process.
I'm not a huge fan of the sweep line algorithm, especially since extending it to 3D sounds like an indexing nightmare and there's no opportunity for parallelism. However, I think with Julian's degenerate snapping, there's no need - the sweep line was a nice way to get from O(n^2)
to O(nlogn)
, but we can do that with our BVH tree anyway (and it's parallel).
So, for the 2D problem (output an ε-valid polygon with e.g. a positive fill rule from an invalid polygon), it could look like this:
For the 3D case, things get a bit more complicated, but follow the same general concept. Before, after step 1, verts went from having exactly 2 edges to having an even number of edges attached. Now edges will go from 2-manifold to even-manifold (number of triangles they're attached to). And instead of outputting sub-edges that divide winding numbers 0 and 1, we need to output sub-polygons of input triangles that divide winding numbers 0 and 1. These polygons will be triangulated as a post process, just like in the Boolean. Steps:
This feels implementable - more serial than the Boolean, but still parallel where it really matters (the BVH broad phase). Curious if anyone can think of an example case where this could break down?
This will be a large feature; I'm not sure when I'll have time to tackle it, but I want to write down my thoughts since I now have a sketch of the algorithm.
Removing overlaps would have two major uses: as the final stage of a manifold fixing algorithm for non-manifold input meshes, and also as a cleanup after
Warp()
that would take care of produced intersections. Technically it could also replace the Batch Boolean algorithm, but that's not a good idea as it probably won't be as fast.In the paper this library's Boolean algorithm is based on, it makes clear that it only works for two input manifolds, not for removing the self-intersections of a single manifold. The reason is that the paper's method of linking edges can be done independently and still guarantee a manifold result, which is why the algorithm is parallelizable, but examples are given that break this guarantee in the single-manifold case.
However, the first half of the Boolean should still work basically the same. The collider will need to be updated so it can automatically skip intersections between a triangle and any edges that share a vertex with it. Then the calculation of intersection points and the winding numbers of vertices can proceed as it currently does, mostly. The verts will check for shadowing with their neighboring triangles, and use their neighbor triangles results to determine which two winding numbers this surface separates. Only verts separating winding 0 and 1 will be kept (strictly-positive fill rule).
The difference is there are a few new vertices that will not be found by these intersections of triangles with edges. These new verts will be the intersection of three non-neighboring triangles, which will be findable because they are also the intersection of three new edges, and this intersection exists if and only if the three new edges share a single set of three triangles as neighbors.
The bigger problem for manifoldness is that previously the new verts on a given partially-retained edge could be paired up arbitrarily (and independently) and still guarantee a manifold result. In the case of these new triple-intersections, that is no longer true - they depend on each other topologically. This means the assembly algorithm cannot be parallelized (that I know of), but by working serially it should be possible to start at an intersection and work our way along each seam, checking any previous neighbor pair-ups and basing the new pair-ups on those to maintain topological consistency.
Additionally, another type of new edge can form, which has only one intersection vert. This is when a manifold is folded, producing an intersection curve that terminates on both ends instead of forming a loop. In the case when a vert opposite the intersected edge (the other vert of either neighboring triangle) is shared with the triangle it is intersecting, then this new edge will go from the intersection vert to this opposite vert.