sphere with a noisy radius rendered in magicavoxel

Generating things with code

part 2 of 2: process and systems

In the first part of this post, we explored the nature of generative systems, we emphasized the importance of data and space in generative design and highlighted the process as a third component.

THE ELEPHANT IN THE ROOM

before we start, I need to disambiguate what will follow from a field that may seem identical: data-visualization. Data-visualization is the field of illustrating — illustrate literally means ‘bring to light’ — a data-set. I won’t go too much into details as it’s not my field of expertise, but there are two main types of visualizations: informative and exploratory. Informative visualizations are meant to give an insightful representation of a data-set, exploratory visualizations on the other hand are more of a tool that allows the user to make sense of a data-set.

PROCESS

We saw the intricate relationship that data maintain with their space of representation and vice versa. A process can be thought of as data-space design. It’s a field with few canonical rules and where creativity arises.

DISTRIBUTION

The first thing we want to do when we found an interesting data-set is to view it in space. This process can be referred to as a distribution ; the fact of displaying our data in space in a plain and legible way. Distribution is a N-dimensional process but I find more intuitive to explain it it 2D so, to keep things simple, we’ll work with 2D data. The example below uses translations to distribute 2D cartesian data points (X/Y coordinates), on a 2D canvas.

grid distribution: points are translated only
polar distribution: points are rotated around a center and translated along a line at this angle
progressively re-scaling a regular polygon
var golden_angle = Math.PI * 2 / ( ( 1 + Math.sqrt(5) ) / 2 );var count  = 1024;
for( var i = 0; i < count; i++ ) {
var theta = i * golden_angle;
var radius = Math.sqrt( i / count ) * h / 3;
circle(Math.cos(theta) * radius,Math.sin(theta) * radius, 5 );
circle(Math.cos(theta) * radius,Math.sin(theta) * radius, 1 );
}
polar distribution based on the golden angle

LERP, NORM, MAP

When it comes to distribution, I can’t think of anything more useful than these 3 methods:

function lerp ( t, a, b ){
return a * (1-t) + b * t;
}
function norm( t, a, b ){
return ( t - a ) / ( b - a );
}
function map( t, a0, b0, a1, b1 ){
return lerp( norm( t, a0, b0 ), a1, b1 );
}

TRANSFORMATION

Once the data are distributed in space, the next process we can think of — as we’re interested in shapes’ generation — is the transformations ; literally the fact of turning something into something different. To tackle this new concept, we will need to be able to compute relations between our data points, namely angles and distances. Assuming we have data points with x and y properties, the angle and distance methods look like this in pretty much any language:

//angle from point a to point b
function
getAngle(a,b) {
var dx = b.x - a.x;
var dy = b.y - a.y;
return Math.atan2(dy, dx);
}
//distance from point a to point b
function getDistance(a,b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return Math.sqrt(dx*dx + dy*dy);
}

RULE-BASED SYSTEMS

Most tesselations of the plane (point symmetries, friezes and pavings) use only the above methods in a specific order to achieve complex looking patterns. By systems I mean the organization of processes to achieve a result, in this sense it is very close to an algorithm. Just like the distribution of data in space requires some simple methods, designing a system also requires some tools the most fundamental of which is probably a grammar.

picture source

L-SYSTEM

I said earlier that there are few canonical rules when dealing with processes, there is one seminal system though and it sprung a constellation of generative systems: the L-system. The ‘L’ stands for ‘Lindenmayer’, a Hungarian biologist who wanted to model plants’ growth in a compact way and came up with this notation. L-systems are a variation on formal grammar, if we set the following production rules:

rules  : (A → AB), (B → A)
n=0:         A           start (axiom/initiator)
/ \
n=1: A B (A→AB)
/| \
n=2: A B A (A→AB), (B→A)
/| | |\
n=3: A B A A B (A→AB), (B→A), (A→AB)
/| | |\ |\ \
n=4: A B A A B A B A (A→AB), (B→A), (A→AB), (A→AB), (B→A)
L-system demo

ITERATION

This principle is probably the simplest to understand if you’ve used a computer already, I used it in almost all the examples so far, it’s the for and whileloops. These statements allow a process to run for a given amount of times or until a condition is met (respectively). Loops will let us apply a process to arrays of objects instead of dealing with individual entities. In the previous examples, I would iterate an angle variable from 0 to PI*2 and use the current iteration value as the angle at which I wanted to place a dot.

FEEDBACK

This is also quite intuitive if you’ve ever animated something with code ; it’s the fact of using the previous state of an object to compute the next. if you want to move a dot from left to right on a screen, you could write something like: dot.x += value and you would be using a feedback ; using the previous x position of the dot to compute the next.

REWRITING

The fact that L-system uses a feedback rather than an instantaneous process allows to read the current state of an object, alter it and only then update (rewrite) the whole object rather than some of its properties. This two-step process is not frequently used but it is very powerful, image filters use it a lot.

TURTLE

In its graphic form, the L-system uses a turtle, it’s the most common form of ‘brush’ or ‘pencil’ when using a graphic API. A turtle can move forward (draw a line), rotate left and right in place and teleport to any point of the canvas. In modern versions, it can also save a state (position/rotation), do something and come back to the saved state. In more recent generative systems, turtles are also called agents ; an augmented data-holder, generally semi-autonomous, that does more than a regular turtle but remains essentially a way of drawing things on screen.

RECURSION

It’s the fact of applying the same process until an exit condition is met. Unlike iterations, the recursive process will call itself repeatedly which makes it very tricky to master as you may well freeze your platform if your exit condition is never met. A canonical example of recursion is the sierpinski triangle where a triangle is recursively subdivided a given amount of times.

TREES and GRAPHS

Another notion introduced by the L-system was the graph. When we unfold the algorithm, generation after generation, a tree-like structure emerges. Trees are a special kind of graphs that are said to be acyclic as they have no loop (cycle) ; there is a single root node and branches cannot be connected to themselves or other branches. By design, a L-system can only produce trees but graphs are very handy data structures. Here’s a minimal Graph code:

var Vertex = function( data ){ /*store the data*/ }
var Edge = function( v0, v1 ){
this.v0 = v0;
this.v1 = v1;
}
var Graph = function( vertices, edges ){
this.vertices = vertices;
this.edges = edges;
}
//create two vertices
var A = new Vertex( data );
var B = new Vertex( data );
//create an edge to bind the two vertices
var E = new Edge( A, B );
//create a graph
var graph = new Graph( [ A, B ], [ E ] );
Minimal spanning tree of a graph

EMERGENCE

Last but not least, the L-system exhibits an emergent behavior. This is the case when you build a system where “larger entities arise through interactions among smaller or simpler entities such that the larger entities exhibit properties the smaller/simpler entities do not exhibit.” (wikipedia). Indeed, when you craft the ruleset and feed the axiom, there is no telling what the resulting outcome will be.

graphic coder

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store