Introducing libcontour
The general concensus is that the task of contouring a heightfield is a solved problem. For example, this algorithm dates back to 1987. I disagree.
There are a number of aspects to traditional marching squares approaches that are deeply dissatisfying to me. The first and most significant is that they require working on a rectangular grid. Anyone that knows me knows that I abhor both discretization and rectangular requirements. I find myself needing contours over general polygonal domains more often than rectangular domains. By this single criterion, essentially all existing contouring libraries are excluded.
The next logical step up would be to use marching triangles on a triangulation.
The algorithm generalizes fairly trivially, and there is even a quality guaranteed algorithm by Boissonat and Oudot that works with a stabbing oracle.
This by itself solves some of my problems, but introduces several more (for starters, depending on CGAL is a big problem).
Contours are notably different from a mesh or polylines.
For one, there is usually some notion of smoothness and curvature that should be captured.
More importantly, to me at least, is that typically I am looking for a partition of the domain, not merely the contours.
Thus, I want closed water-tight loops that exactly partition the domain; I want an arrangement.
Contours also need to deal with the nasty problem of saddle points, closed interior loops due to local extrema, among others.
At this point, it should be quite clear that something commonplace like Matlab's contourf is not at all sufficient, not least of which is due to my utter distaste in having to specify a sampling density--why can't it figure it out itself?!
Thus, I asked for libcontour.
It began as a header file that I sketched out; an interface that I desired. Claude took it from there, first asking what algorithm to use; marching triangles was a candidate but pretty much immediately ruled out. Since the emphasis is on quality and correctness, we settled on an multi-phased approach, in which a first step is to perform an analysis of the function over the domain in order to compute "all" critical points (extrema and saddles). I had already baked this assumption into the interface, but it was good to see it confirmed. Claude then suggested applying topological checks and Morse theory to checksum the critical points. This was a genuinely new concept to me, so I was suitably impressed that it was pulling in computational topology. Incidentally, this is also why the provided function must be twice differentiable; the Hessian is used to not only locate but also classify critical points.
The second step is to actually trace the contours. Since the extrema have already been determined, the number of contour components is known. Each contour is traced using gradient information with adaptive step sizing. Contours traced this way may still be excessively tessellated, so an additional contour simplification step was added.
Next, these contours may be stitched with boundary segments to construct a partition of the domain, a feature that is necessary for many engineering disciplines, but which every other contouring library lacks. The final topologically necessitated complication is that the partition regions may have holes due to local extrema internal to the region, so a final flattening step may be required to produce simply connected regions.
What started out as a fairly simple ask turned into a fairly involved library totalling about 2600 lines of code. Looking at the result, it's clear I could never have written it, since it requires a much more serious understanding of numerical analysis than I have, and applies topological concepts that are relatively foreign to me. The icing on the cake was when I asked it for a visualizer, and it whipped up an imgui-based solution that was pretty much exactly what I had in mind, with almost no guidance.
I think the lessons here for me are:
- (Near) frontier level intelligence far exceeds my own knowledge, and is more capable than me, with the right prompting.
- There is no reason for me to implement things like this for which I'm fairly confident there should exist a solution, but where I lack the detailed background to actually formulate the solution properly.
- Taste is all that's left.
Getting libcontour implemented has been particularly satisfying to me because it resolves longstanding issues with parameterizing partitions. Most of my work and interests involve two dimensional regions, and their parameterization has always been painful. The simplest partitions are those that are analytically described, e.g. linear sequences of stripes. Anything more complicated would tend to require contouring, and as we have established, there has never been a good solution. This is the first many itches that I have finally been able to scratch.