Hi. A friend asked me how to create 2D patterns and also how to make not only rectangular patterns but other shapes, so I thought I could answer here which might help someone else too
I’m going to assume you understand the following program:
1A - Randomness
size(900, 50);
background(255);
noStroke();
for(int x=0; x<30; x++) {
fill(random(255));
ellipse(x*30 + 15, 25, 28, 28);
}
2A - First pattern
So how can I avoid randomness and make a pattern? I’ll start by alternating two colors. To do that I will check if the current circle is odd or even, and choose a color accordingly. To do that I replace the line that sets the color (fill(random(255));
) with the following:
if (x%2 == 0) {
fill(#C12969);
} else {
fill(#6BD8AA);
}
You could understand the expression if(x%2 == 0)
as “if it is the first step in a loop of 2 steps…”. We could also write if(x%2 == 1)
which would mean “if it is the second step in a loop of two steps” in case we wanted to target the second element.
In a loop of two steps it doesn’t make much sense because we can use the else
case for that, but we will see later why this is useful in longer loops.
Note: I explain this in episodes 72 and 73 in Fun Programming
So going back to the last program, we are saying that if it is the first step in a loop of two use one color, otherwise use a second color. Basically alternating colors.
For brevity, we can rewrite the whole if expression using the shorter ? :
expression (reference):
size(900, 50);
background(255);
noStroke();
for(int x=0; x<30; x++) {
fill(x%2 == 0 ? #C12969 : #6BD8AA);
ellipse(x*30 + 15, 25, 28, 28);
}
I will use the ternary operator from here on as it is much shorter. When you see A ? B : C
it means that “if A is true, use B otherwise C”.
3A - Pattern of length 3
Back to our pattern. How to create a pattern like BCC BCC BCC BCC…? Simple:
fill(x%3 == 0 ? #C12969 : #6BD8AA);
You can interpret that as: in a pattern of length 3, if we are at the first element choose the first color, otherwise (second and third elements) use the other color.
4A - Give me five
One more: in a pattern of length 5, use the first color for the center item, otherwise use the second color:
fill(x%5 == 2 ? #C12969 : #6BD8AA);
5A - Not just one item per pattern
What else could we do? Maybe BBCCC BBCCC BBCCC? That looks like this:
fill(x%5 < 2 ? #C12969 : #6BD8AA);
How does this work? x%5
will always gives us one of these values: 0, 1, 2, 3 or 4. If we check if that value is less than 2, we get true
for 0 and 1. That why the pattern looks like BBCCC BBCCC BBCCC.
6A - Two conditions
What about a slightly more complex one like BCBCC BCBCC BCBCC?
For that one we might want to check if we are dealing with item 0 or 2 in each loop.
Like this:
fill(x%5 == 0 || x%5 == 2 ? #C12969 : #6BD8AA);
7A - Combine patterns
Looking at the last one might give us an idea for more strange patterns. What if instead of checking against two loops of length 5, we check against two loops of different lengths? This will give patterns that take longer to repeat. Like this one:
fill(x%3 == 0 || x%5 == 2 ? #C12969 : #6BD8AA);
This loop will repeat every 15 steps because lengthOfLoop1 x lengthOfLoop2 = 15 (and both lengths are prime numbers). If we had lengths of 3 and 6 the total length would still be 6, not 18. If you are curious why I can explain it below.
1B - Now in 2D - randomness
Let’s do it all again but now in 2 dimensions instead of just 1. This is the basic program:
size(200, 200);
background(255);
noStroke();
for (int x=0; x<10; x++) {
for (int y=0; y<10; y++) {
fill(random(255));
ellipse(x*20 + 10, y*20 + 10, 18, 18);
}
}
2B - First 2D pattern
If we use the old approach
fill(x%5 == 2 ? #C12969 : #6BD8AA);
there’s something missing. We do have a grid, but nothing changes in the vertical axis.
3B - Using both x and y
What if we take the approach from 7A and combine two patterns, one dealing with x
and one dealing with y
?
fill(x%5 == 2 || y%3 == 1 ? #C12969 : #6BD8AA);
Ok, that’s better. So we have a horizontal pattern of length 5 looking like CCBCC and a vertical pattern of length 3 that looks like CBC. But it can be very “grid-like” for some tastes.
4B - Tilt it
What if we combine x and y in the same comparison? For this it’s important to put into parenthesis whatever expression we put into the modulo operation. a+b%c
is no the same as (a+b)%c
. In the first of these examples b%c
is calculated first and then added to a
.
fill((x+y)%5 < 2 ? #C12969 : #6BD8AA);
5B - Using the cell index
More complex patterns can be achieved by knowing the index of the cell we are dealing with, so in this example of a grid of size 10x10, we would get an integer value between 0 and 99. How can we calculate that number? Simple:
int index = x + y * 10; // 10 is the width of the grid.
Now we can use index to decide the color, instead of x
or y
.
int index = x + y * 10;
fill(index%7 == 0? #C12969 : #6BD8AA);
The effect is like counting from the first cell in the grid to the last one and highlighting one every 7.
6B - Use the cell index twice
For added complexity, lets use the index twice:
fill(index%7 == 0 || index%5 == 2 ? #C12969 : #6BD8AA);
Now that I find very interesting because at this scale it seems to be somewhere between random and pattern.
7B - More colors
I’ll go back to using if
statements for this one, as using two ternary operators in the same line gets quite unreadable:
if(index%3 == 0 || index%5 == 2) {
fill(#C12969);
} else if(index%7 == 3) {
fill(#E8CB69);
} else {
fill(#6BD8AA);
}
8B - Priorities
Note that changing the order of those conditions can alter the pattern. For example you can see that yellow has less priority than magenta because it’s the second condition. I’ll move yellow up to be the first condition:
if(index%7 == 3) {
fill(#E8CB69);
} else if(index%3 == 0 || index%5 == 2) {
fill(#C12969);
} else {
fill(#6BD8AA);
}
Very different feel!
9B - Masking
What if we don’t want a rectangular shape for the grid? The simplest example might be creating a circular pattern. We can do that by getting the distance from the current cell to the center of the grid. If that distance is too high, don’t draw anything. That just one if
statement away.
size(200, 200);
background(255);
noStroke();
for (int x=0; x<10; x++) {
for (int y=0; y<10; y++) {
if(dist(x,y, 4.5, 4.5) > 4) {
continue;
}
int index = x + y * 10;
if(index%7 == 3) {
fill(#E8CB69);
} else if(index%3 == 0 || index%5 == 2) {
fill(#C12969);
} else {
fill(#6BD8AA);
}
ellipse(x*20 + 10, y*20 + 10, 18, 18);
}
}
10B - Noise mask
If you want something less regular than a circle you could use noise
instead:
if(noise(x*0.3, y*0.3) < 0.5) {
continue;
}
The reason this works is because noise()
in Processing returns values between 0 and 1, so sometimes above 0.5 and sometimes below. I multiplied x
and y
times 0.3 to zoom in into the noise. Otherwise the shape may be too irregular.
Ideas to try: combine both dist
and noise
(maybe the cut off point is not 0.5 but the distance to the center), instead of changing colors change sizes or shapes instead.
I’ll end this tutorial here. If you try this it would be cool if you post your results in this thread for others to see