Cloverfield walkthrough
written by r1b2
Most of the time, my projects will start with a reference picture. This is the picture I took, during a walk in Chimney Bluffs National Park (upstate NY).
Zooming into the reference picture, it was apparent that there were several types of grasses coexisting. There were clovers, 2 types of grass blades and some plants with larger leaves. The project mostly consisted in modeling each of these.
The Shape class
For this project I used a class (unimaginatively called Shape), which really is little more than a wrapper around an Array of {x,y} points.
I'll show a few methods of the Shape class below. Drawing a shape is as simple as:
draw(layer)
{
layer.beginShape();
for (let p of this.pts) { layer.vertex(p.x, p.y); }
layer.endShape();
}
Of course I also have the option of using my ink-style signature drawing style, using a drawer class that draws lines by splitting them into many points, displacing the points slightly, varying the line weight and randomly adding analog effects like splatter:
analogDraw(layer, drawer)
{
for (var i = 0; i < this.pts.length - 1; ++i)
{
drawer.draw(layer, this.pts[i], this.pts[i + 1]);
}
}
When drawing shapes you very often want to erase what's underneath - so you can draw shapes on top of each other without the bottom ones showing through. In order to do this, I set the layer in erase mode and draw the shape:
eraseInside(layer)
{
layer.push();
layer.erase();
layer.noStroke();
layer.fill(0);
this.draw(layer);
layer.noErase();
layer.pop();
}
One of the most powerful features of Javascript, is that functions are objects, and can be used as variables or parameters to other functions. For example, this code builds an array of points along a function f:
function buildCurve(f, npts)
{
var pts = [];
for (var i = 0; i < npts; ++i)
{
var pt = f(i / (npts-1));
pts.push(pt);
}
return pts;
}
This is very useful to generate complex shapes. This for example, generates a circle with 24 points.
var circle = new Shape(buildCurve((t)=>{x:Math.cos(2*PI*t), y:Math.sin(2*PI*t)}, 24));
Drawing clovers
Armed with this, all I need to draw a clover, is a function that will display a heart-like shape, use it to generate a Shape, and draw that Shape 3 times, with a 2*PI/3 rotation between each leaf.
WolframAlpha has a nice collection of heart functions. I used this one, with some tiny randomization of all the constant to get slightly different shapes each time:
x=16sin(t)3,y=13cos(t)−5cos(2t)−2cos(3t)−cos(4t)Given this, the following code will draw a n leaves clover:
var s = new Shape(buildCurve(cloverFunction, 64)); // 64 points heart shape
for (var i=0;i<nleaves; ++i) // draw nleaves
{
s.eraseInside(drawingLayer);
s.analogDraw(drawingLayer, drawer);
drawingLayer.rotate(2*PI/nleaves);
}
Here are a few of these:
They looked a little bit too perfect for my taste, so I eventually added a little bit of randomization to the rotation as well so they don't divide the circle so perfectly.
Drawing grass blades
For the grasses, I guess I could have used Bezier curves. I don't like Bezier curves, to be honest. They don't like me either. I always struggle to get them to look the way I want. So I used math curves again.
This desmos page shows the formula I used: 2 portions of a sine curve, displaced and scaled so they start and end at the same point.
In Javascript this becomes:
function grassFunc(x, k, a)
{
var d = (x) => a*x*x*x*k+x*(1.0-k);
if (x<0.5)
{
var t = 2*x*6*PI;
var dt = 2*d(x)*6*PI;
return {x: t, y: Math.sin(dt/4)+Math.sin(t/6)/3};
}
else
{
var x0 = 1-x;
var t = 2*x0*6*PI;
var dt = 2*d(x0)*6*PI;
return {x: t, y:Math.sin(dt/4)};
}
}
const k = rrnd(0,1);
const a = rrnd(0.05, 4);
var f = (x)=>(grassFunc(x,k,a));
var s = new Shape(buildCurve(f, 64));
Varying the displacement and scaling generates a lot of nice, organic looking blades, like in the picture below.
Drawing the larger leaves
The larger leaves came from WolframAlpha again, this time using the Cannabis curve with the constants slightly randomized.
SIMPLE LEAVES
There are also smaller leaves using the "folium" equation:
r=cos(θ)(4sin(θ)2−1)Drawing daisies
These are actually very simple. The individual petals start their life with a circle Shape, that is stretched on one axis to turn into a thin ellipse. This ellipse is then drawn and rotated, just like for the clover leaves. There are just a lot more rotations (between 15 and 20, picked randomly).
Composition
With all the above elements done, all it takes is layering a lot of them to achieve beautifully complex results. Here is an example with about 500 of each.
In order to achieve more natural looking distribution of the elements, I used a system of "attractors" - for example for clovers, I pick a random point as the attractor center, and then position clovers by picking a random point in the canvas, and moving it towards the attractor center with a random weight. This has the effect of skewing the distribution of clovers around the attraction point.
I added a "background" drawing modes for all the elements. In this mode they are drawn with a dark fill. Then I layered a bunch of background elements, followed by a bunch of foreground ones.
The last step was to get a little more variety in the outputs. I used tried and true recipes:
- different paper colors
- different background textures
- different probabilities to make some items more prominent than others
- 4 leaves clover
- color versions (that actually turned out quite beautiful) where each element has its own random shade of green.
There was also a rare "pink daisy" version that unfortunately, did not happen in any of the minted pieces. It is visible in an objkt collection of 13 1:1 pieces that I released afterwards.
Outtakes
While experimenting with the generator, I ended up with interesting pieces that unfortunately, were taking too long to generate. They are available as 1:1 objkt pieces as well, including 7 "Sunflowers" versions (where 100% of the sales and royalties go to Ukraine).
Closing words
I hope you enjoyed reading this. Cloverfield is one of these pieces where the outputs look very intricate, yet the generation itself is actually rather simple, and the complexity comes from layering a large number of elements.