Making patterns with modulo

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 :slight_smile:

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);
  }
}

1580728706699

2B - First 2D pattern

If we use the old approach

fill(x%5 == 2 ? #C12969 : #6BD8AA);

1580728917903

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);

1580729042514

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);

1580729298165

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);

1580729677894

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);

1580729791210

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);
    }

1580730045130

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);
    }

1580730220818

Very different feel! :slight_smile:

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);
  }
}

1580730454031

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;
}

1580730664343

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 :slight_smile:

7 Likes