Drawing Squiggly Lines
According to the online cambridge dictionary, squiggly lines "curve and twist in a way that is not regular".
I recently came accross a background generation website that offeres a "gradient topography" variant, essentially mimicking a topograhic map, but with smoother curves and a more interesting color scheme. (For other examples of stuff in this style, see the default backgrounds that come with macOS Big Sur and Monterey, for example.)
I like this style, and I wanted to create something similar, but dynamically. That is, I wanted to be able to create dynamic shapes with (seemingly) irregular but smooth curves.
You can find my experimentation on this topic in v0/squiggly-stuff. Initially, I was using the canvas API, and most of the algorithm development was with that API. Later I switched to SVG, mostly to try out something else, but also because the canvas API is kind of cumbersome.
What follows is a presentation of a method for randomly generating polygons that is easy to parameterize and has some other useful properties. Then I'll showcase an algorithm I found on the web that smoothes out the edges of a polygon, creating the shapes we are looking for.
Generating a Polygon
Let's start with polygons for now and worry about getting rid of the sharp edges later. We need the polygon generation to be random because it is our primary source of irregularity—the smoothing algorithm doesn't contain any randomness.
Okay, we want to randomly generate a polygon. The naiive way to go about this is to just generate some random points in a given area:
click to regenerate
This is, as it turns out, a suboptimal solution. The generated polygons are often self intersecting, we don't want that for aesthetic reasons. Also, this way of generating points is hard to parameterize (aside from the obvious randomness). For example, assuming an ordered set of points, we might want two points that are adjacent in the set to also be close to each other in the coordinate space.
An improvement one could make is to divide the area into slices of a circle, and to generate one point per slice—while choosing the slices in clockwise or counter-clockwise order (as in, not randomly).
click to regenerate
The improved version solves the problem of generating self-interesecting polygons, but the polygons still don't look consistently good: Some more parameterization is necessary.
This concept of using a circle to ensure that the polygon not self-intersecting motivates a somewhat more complex approach: Distribute a given number of points randomly but evenly along a circle with radius r
, then choose points some random distance (bounded by spread
) and direction around them.
click to regenerate
Arguments to the algorithm are radius and spread. Dashed circle represents radius, solid circles represent spread. Colored dots are initial points, outlined points are the final output. Note that the initial points are spread evenly but not randomly, this was omitted for simplicity.
Now we have an algorithm that gives us random polygons that will never self-intersect, and we also have a measure of control over how the generated polygons will look like.
click to regenerate
Demonstration of polygon generation, with every polygon the number of edges increase by one.
This is the algorithm that will be used for generating the shapes in the rest of this post.
Smoothing it out
The algorithm I will be presenting here is based on a post (archive) by the late Maxim Shemanarev, known for Anti-Grain Geometry. It takes a polygon as input and generates Bézier curves that smoothly interpolate it. As far as I am aware he is the inventor, I'm just presenting the idea in a different context.
We start out by getting the midpoints of the edges of our starting polygon, and connecting the midpoints of edges that share a vertex (corner). We then divide each such connecting line into two parts of the same proportionality as the edges whose midpoints it connects. We call the point between these proportional parts the anchors.
click to regenerate
Points with a colored outline are midpoints, filled points divide the connecting lines between midpoints into parts proportional to the edges.
Finally, we move the connecting lines such that their anchors are at the same position as the respective vertex. The ends of these lines, the previous midpoints, now serve as control points for cubic Bezier curves. (I synchronized the previous and following illustrations, hopefully it helps to better understand the transformation.)
click to regenerate
Note that a given vertex has two control points associated with it, one for the "inbound" curve, and one for the "outbound" curve. Because these points lie on a straight line, the slope of the inbound curve is equal to that of the outbound curve at the point of contact. This also means that a pair of control points can be scaled along thir respective line to control the sharpness of the curve.
If an edge is relatively short, respectively the inbound and outbound control point of its two verteces will be "weak", that is close to the respective vertex. So a short edge will not have overblown control points that lead to curls or similar. In that same scenario, an adjacent and relatively large edge will have a relatively stronger outbound control point to make sure the curve isn't too flat at any point.
I think it's fascinating how elegantly this simple algorithm can solve such a nontrivial and abstract problem.
The Fun Stuff
Now that we're able to generate an aesthetically fitting polygon and smooth out its curves, I'll show off some examples.
Gradient Topograhy (the original goal)
click to regenerate
Outline Topography (a variation of the above)
click to regenerate
That's it, for now.