Intro
Many programmers who started in the 80s usually began by writing code to draw pretty things on the screen. I did so too, mashing together random programs that were essentiall coloring each screen pixel with a color given by F(X,Y) - usually in my case, a mishmash of SIN, COS, TAN and whatever else I found in the manual.
After several decades of programming for fun and profit, in 2019, I rediscovered this pastime through dwitter.net and then off late on fxhash.
I will be posting a number of articles explaining how many of my generative art pieces work. Code will be shared on Github
My coding style is minimalist - having been schooled on dwitter, I tend to write minimal code - all my generative art pieces are under 5 kilobytes, even with clean indentation and verbose Javascript. No external libraries are used.
Grassy Knoll
I didn’t set out to draw a grassy hill at all - I was going for a coral like structure, but serendipitously I discovered how to draw something that looks like grass.
Purely, coincidentally the much talked about word “KNOLL” was on my mind due to wordle #219, so I stuck with that name.
Evolution of the code
The basic algorithm started as a derivative of “Linear Aggregation” - which works as follows:
Choose a random location X at the top of the canvas
“Drop” a circular element from the top (give it a random color)
If the element reaches the ground and remains in its color, stop and repeat from the top
If the element touches a previously dropped element, it “sticks” there, and takes on the color of the element it touched
Implementing the basic algorithm results in outputs like this:
If you make the circles smaller, and choose colors associated with corals, it does tend to look somewhat like coral growth.
A lot of good generative art for me, comes from meddling without a goal rather than trying to get something intentionally.
The first experiment I tried was - what if instead of “sticking” the falling elements right where they fell, let them stick at the left or right on top of the one they fall on, based on which side they fell. The amount left or right they stray is randomized, but can take a value far more than the size of the element
This resulted in :
You can see how the elements tend to drift on each side - some are not exactly physically connected to the ones they “fell” on because they are shifted laterally
As I kept playing around, I tried thin and long vertically placed ellipses instead of circles
It looked like grass, so I made it green without random colors:
At this point I decided to start changing it towards the goal of looking like a lawn rather than experimenting on the basic form.
First of all, grass needs some color variation, so how about having a small variation of the initial color of each element, between yellowish green and bluish green on the HSV spectrum?
Looks nice, but too patchy - this is because all elements that contacted others imbue the same color.
What if we start all elements with one shade, but when an element touches another, rather than making the new one imbue the exact same color as the one it touched, change the hue randomly by a small bit?
Much better - its starting to look natural.
Note that the code for caculating if the dropped element touched another was treating the elements as circles, even though the items are drawn as ellipses - there is some overlap in drawing elements, which, added to the fact that there is a slight transparency in the color, gives a more natural effect.
Now, how about tilting the “grass” left or right by a random amount, based on whether it touched another on the left or right?
Also placing each touched element a little higher (since they are now vertical and we need not overlap so much)
Now it looks soft and lush enough to sit on, even if a bit over-saturated like an instagram filter.
A final tweak I did was to draw the ellipses from angle 0 to 4 radians rather than 0 to 2*PI. Each “blade” looks like this:
Although the blades are really tiny onscreen, this minor tweak does result in a visually different look.
Before we move on to how it turns into a hill, lets talk about performance.
Performance
I hate slow code, and especially in a language like JS, performance is not available by default.
The above algorithm relies on “dropping” elements - intially I implemented it with animation - little circles would literally fall from above and accumulate.
I first implemented a very naive collision detetction algorithm - just check every existing element against the new one and if the distance between their centers is less than a diameter, they have touched.
This becomes molasses slow, once a few thousand elements accumulate - especially if you are iterating from Y = 0 upto Y = height and checking each time.
Unacceptable.
Should I implement a very sophisticated algorithm?
Do I need to start looking at game dev articles about collision detection?
No - I’d rather work out some stuff by common sense first.
Rather than looking for the proverbial needle (potential element the current dropping one will fall on) in the hay-stack by checking all elements, what I needed was to look for the highest element that is under the current one.
Instead of using a costly distance formula (which has two multiplies and an addition - X*X+Y*Y > RSQUARED
), simply find the highest element whose X co-ordinate is within R distance from the current “dropping” elements X.
The first thing I realized was - since the items are piling up, and getting appended at the end of an array, the vertically highest elements (lower Y co-ordinate) are at the end too.
By iterating backwards, you can be sure that the topmost elements are in the last few percent of the array - you never need to iterate more than that.
So the algorithm is:
Choose a random X for the new element
Iterate backwards over the elements we have until we find one whose X is close enough that the new element would “fall” on it
Treat that element as the one it “fell” on
This works fast enough to place a few thousand elements per animation frame - perfect for the use case. If I needed to generate a million of these, maybe not fast enough, but then I would probably not be using Javascript or such a simple heuristic.
Landscaping
I had reached the point where I had decent looking grass. It seemed like a picket fence and an endless plain of grass would be the final depcition.
In order to get the perspective effect, the size of each successive element was reduced
Now its a matter of placing a picket fence along the boundary of the grass “horizon” and filling the black background above it with sky blue.
This seemed like a simple matter, but turned out to be the hardest thing.
Because the algorithm sometimes creates areas of black emptiness (which look like shadows) the topmost grass layer may be like steps.
Even in the above sample - you can see that the extreme right has a sudden deep step.
I needed to somehow smooth the horizon - for which I used a brute force “grass detection” scheme.
Just iterate over all X co-ordinates and see what Y coordinate is non-black.
Here is how that boundary looks - the red line is the Y at which the topmost grass blade is at each X.
Very spiky, noisy and steppy!
What to do?
Smooth it…
How?
Just make each point in this line the average of its neighbours, repeat a few dozen times.
After 20 smoothings:
After 200:
Now we can use this as a clip region to fill the sky
It looks a bit weird - like a cardboard cutout - also the reduction in grass size is a lot and the boundary looks like a far off vista, rather than the place to draw a picket fence.
It may have been OK to stop at this point and call it a day, but I wanted to try a bit more.
So I chucked the idea of a fence and reduced the perspective effect - simply by drawing fewer blades of grass, so they dont reach the tiny size seen above.
Looks like a closeby grass mound, but still a cardboard cutout.
How about I add a random value to each Y co-ordinate of the border?
Much much better!
Let me add some landscaping - add 170 degrees of a sine wave to the boundary curve, shifted by a random number of degrees, with an amplitude 1/7th of the canvas height.
We might be cutting well into most of the initial boundary, but at the points we dont, the original will remain and thus the smoothing we did is essential.
Now it looks pretty much like a Lo-Fi picture of a closeby hill.
Almost done!
Finishing touches
I wanted clouds in the sky and flowers on the grass.
Clouds were easy - I’ve been using the same technique in many of my dweets and NFTs :
Make a separate canvas
Set a blur filter on its context - like
ctx.filter=’blur(10px)’
This is not the same as setting the filter on the canvas.style - that filter is applied only on whats shown on the screenFill with sky color
Draw thousands of random lengthy (on the X axis) rectangles in white
Draw this canvas onto the main canvas
The reason a separate canvas is made is because you cannot undo the blur filter (I needed to draw flowers later) if you set it on the main canvas - some browser versions create weird artifacts if you do that.
There is some indication that these clouds are actually rectangles, but it is decent enough. For better clouds you can draw ellipses or use a noise function or other advanced cloud algorithm.
Flowers were not easy - I tried drawing daisies with 24 petals and a random sinusoidal stalk. Whatever I tried, they looked like they were cutouts placed on the landscape.
Scaling them according to the perceived perspective distance was just not happening right.
So I chose the low tech approach - I wasn’t trying to draw a photorealistic rendering, just trying to do the maximum I could in minimal amounts of code.
The solution was very simple, just draw one in a thousand blades of grass in a flowery color, with mild random transparency.
Also draw them differently shaped from the grass elements - like a cup.
Since incoming grass overdraws on top of these, they get irregular like real flowers with petals.
And voila:
Not a bad deal for about 5 KB of actual code (probably even less if minified dwitter style) - perfect for the style I aim for.
The whole code with comments is available on this github gist:
Grassy Knoll
Generative art - drawing a grassy hill with code
Brilliant, informative, and entertaining!
Brilliant, informative, and entertaining!