Skip to main content

Command Palette

Search for a command to run...

11. The Matrix - Enter into the Matrix

Updated
10 min read
11. The Matrix - Enter into the Matrix
A

I'm a dedicated software engineer with a passion for bringing hardware and software together to create robust and efficient solutions. I thrive on optimizing performance, managing power consumption, and ensuring the reliability of devices from concept to deployment.

🦊

Do you ever wake up in the morning and think, 'I should make the digital rain effect from the Matrix franchise? Well, I did and so here we go. For those of you who don't know, the Matrix franchise had a well-known effect called the 'Digital Rain'. This effect features random sets of characters falling across a black terminal. Sort of like in this video. Let's see how to make an animation like this on Processing...

First step of course would be to find the correct font, obviously 😐. I checked several options, but the one that stood out for me was the font called Matrix Code NFI Font by Norfok Incredible Font Design. You can find it in www.fontspace.com.

I downloaded the font and converted it to be used in Processing. This is pretty straightforward. Just download the font and you will get a zipped archive.

  1. Extract the archive and you will see a font file called MatrixCodeNfi-YPPj.otf inside.

  2. Double click and install this font to the system.

  3. Create a folder titled data in the root folder of our sketch.

  4. Open Processing application.

  5. Go to Tools -> Create Font....

  6. Scroll through the list of fonts and select the font we just installed. (MatrixCodeNFI)

  7. Change the filename to font.vlw.

  8. Click on OK.

  9. A file called font.vlw will be created in the data directory. If it's created somewhere else, copy it to the data directory.

Now that the fonts are ready, we will move onto the actual sketch. First, we'll start with some settings. Here, we will setup the canvas size, load the font, prepare the sizes and counts and create a copy of the charset.

void settings() {
  size(1800, 1000);
  font = loadFont("font.vlw");

  column_size = width/COLUMN_COUNT;
  row_size = column_size;
  row_count = ceil((height * 1.0) / row_size);
  text_size = column_size - (TEXT_OFFSET * 2);

  System.arraycopy(CHARACTER_SET, 0, characters, 0, CHARACTER_SET.length);
}

The column size is calculated with the width and number of columns we have defined to be included. I've also approximated the row size to be the column size. The number of rows are defined by the height and the size of the row that we're defined. Here, we take the ceiling to allow for an extra row below instead of keeping an empty row. This creates a seamless visual that span the whole window.

I've also defined the CHARACTER_SET that will be used for text streaks. The set is as below.

char[] CHARACTER_SET = {
  '!',      '"',      '#',      '$',      '%',      '&',      '\'',
  '(',      ')',      '*',      '+',      ',',      '-',      '.',      '/',
  '0',      '1',      '2',      '3',      '4',      '5',      '6',      '7',
  '8',      '9',      ':',      ';',      '<',      '=',      '>',      '?',
  'A',      'B',      'C',      'D',      'E',      'F',      'G',
  'H',      'I',      'J',      'K',      'L',      'M',      'N',      'O',
  'P',      'Q',      'R',      'S',      'T',      'U',      'V',      'W',
  'X',      'Y',      'Z',      '[',      '\\',     ']',      '^',      '_',
  'a',      'b',      'c',      'd',      'e',      'f',      'g',
  'h',      'i',      'j',      'k',      'l',      'm',      'n',      'o',
  'p',      'q',      'r',      's',      't',      'u',      'v',      'w',
  'x',      'y',      'z',      '{',      '|',      '}',      '~'
};

This is copied to a temporary buffer called characters. The reason is that we intend to inject glitches into the streaks which will be apparent later. This makes sure the original list stays intact without worrying about losing characters. Of course you can modify the original character set according to your requirements and font.

Next, we will move into the setup.

void setup() {
  background(BACKGROUND);
  frameRate(FRAMERATE);
  textFont(font);

  for (int i = 0; i < (int)random(0, COLUMN_COUNT); i++) {
    buffer.add(new Streak((int)random(0, COLUMN_COUNT), (int)random(0, row_count)));
  }

  textSize(MESSAGE_SIZE);
  fill(STATIC);
  textAlign(CENTER);
  text(message, width/2, MESSAGE_Y);

  delay(MESSAGE_DELAY);
}

We set the background colour, set the frame rate, and set the text font. The hallmark feature of the digital rain animation are the streaks of characters that fall from the top of the screen. To define the behaviour of such a streak, we will define a class called a Streak. We will see how this works later. A critical feature is to have a buffer of such streaks to allow them to appear and disappear on the screen. To do this, we have a buffer variable that will hold the streaks that are visible on the screen at any given time. At the start, we will add a random number of streaks to the buffer at random x and y positions.

This is followed by the text size, defining a static font, aligning to the center and print an initial message defined in message variable. This is followed by a delay. The message is as follows:

String message = "PRESS THE SPACE KEY TO EXIT THE MATRIX\nCLICK ANYWHERE TO GENERATE A GLITCH";

Before moving into the draw function, we will look at the Streak class. The structure is as follows:

public class Streak {
  private int x, y;
  private int cursor = (int)random(0, characters.length/2);
  private int lives = STREAK_LIVES;
  private int state = 0;
  private int floor = cursor;
  private int ceiling = (int)random(cursor + 1, characters.length);

  public Streak(int x, int y) {
    this.x = x; this.y = y;
  }

  public void draw();

  public boolean dead();

  private void draw_char(int j, color colour);
}

There are a few private variables defining the characteristics of the streak. The x, y coordinates, cursor position at the start, number of lives remaining, current state, floor and ceiling: which define the subset of characters from the main character set that are utilized in this streak.

I believe x and y are self-explanatory. Also, floor and ceiling. A streak has two main states: alive and dead. Alive state is when the streak is visible in the screen. Dead is when it disappears from the screen.

An alive streak will be in two main states: active and passive. The streak starts its life with the cursor at the first character of the sub-characters buffer. The lives will be at maximum and state will be 0. (alive) If you check the animation, a streak starts with nothing. Then one after the other, characters from the buffer appear one after the other until the end of its buffer is reached. The cursor is the last character displayed by the streak. Once it reaches the end of its buffer, the cursor is gone.

To emulate this, for each time the draw() function is called, we increase the cursor. When the end of the buffer is reached, we keep the cursor in place, but start decreasing the lives. This leaves the streak visible in the screen until the lives reach zero, at which point we change the state to 1. (dead) While the cursor is not at the ceiling, we draw the cursor or the last character currently visible on the buffer as white. The characters we print are from the floor to the cursor only. The rest of the visible characters are left as green. When the ceiling is reached, we display all characters as green. When dead, we do not print the characters.

You can see this behaviour in the following code snippet:

  public void draw() {
    if (this.state == 0) {
      if (this.cursor >= this.ceiling) {
        draw_char(this.cursor - 1, STATIC);
        this.lives--;

        if (this.lives <= 0) {
          this.state = 1;
        }
      } else {
        draw_char(this.cursor, CURSOR);
        this.cursor++;
      }

      for (int j = this.floor; j < this.cursor - 1; j++) {
        draw_char(j, STATIC);
      }
    }
  }

The dead() function is simply there to return if the streak is dead or alive.

  public boolean dead() {
    return (this.state == 1);
  }

The draw_char() function draws the characters to the screen according to the logic we discussed under draw().

  private void draw_char(int j, color colour) {
    textSize(text_size);
    fill(colour);
    textAlign(CENTER);

    if (this.y + j >= row_count) {
      text(characters[j], (this.x * column_size) + (column_size / 2), (this.y + 1 + j - this.floor - row_count) * row_size);
    } else {
      text(characters[j], (this.x * column_size) + (column_size / 2), (this.y + 1 + j - this.floor) * row_size);
    }
  }

Now that we understand how the streaks work, we will move into one of the most critical functions of the code. The glitch() function.

void glitch() {
  for (int i = 0; i < character_glitch.length; i++) {
    if (character_glitch[i]) {
      characters[i] = CHARACTER_SET[(int)random(0, CHARACTER_SET.length)];
    }
  }

  if ((int)random(0, GLITCH_ENTROPY) == 4) {
    int pos = (int)random(0, character_glitch.length);
    character_glitch[pos] = !character_glitch[pos];
  }
}

The purpose of this function is to introduce glitches into the streaks. Remember we discussed that we need a separate copy of the original charset. This is why. We have a separate array which define if each char in the charset is glitched.

char[] characters = new char[CHARACTER_SET.length];
boolean[] character_glitch = new boolean[characters.length];

What we do in the glitch() function is, we first go through each char of the characters. We check if the corresponding character_glitch is enabled or not. If a glitch is enabled, we glitch that specific character. IE, we pick a random character from the CHARACTER_SET and replace this character with that.

Next, we randomly pick some characters from the character_glitch and flip their state: stable or glitched. This is what creates the iconic glitching effect of the streaks.

Since each streak in the buffer is tied to the characters, when a glitch is introduced, this is visible across all streaks.

Next we will move onto the draw() function. This is pretty straightforward.

void draw() {
  fill(BACKGROUND >> 16 & 0xFF, BACKGROUND >> 8 & 0xFF, BACKGROUND & 0xFF, FADE);
  rect(0, 0, width, height);

  if ((random(0, 10) < STREAK_ENTROPY) && (matrix)) {
    buffer.add(new Streak((int)random(0, COLUMN_COUNT), (int)random(0, row_count)));
  }

  for (int i = 0; i < buffer.size(); i++) {
    Streak streak = buffer.get(i);

    if (streak.dead()) {
      buffer.remove(i);
      i--;
    } else {
      streak.draw();
    }
  }

  glitch();
  
  drawTopic();
}

We first fill the screen with black accompanied by an alpha value. This allows the dead streaks to fade away smoothly. We also make sure to add some new streaks to the buffer to replenish any that have gone dead. Then for each streak, we remove it from the buffer if it's dead, and draw() it if it's alive. Then we introduce the glitch().

We also draw the topic using the drawTopic() function.

void drawTopic() {
  String topic_str = "";
  for (int i = 0; i < topic.length; i++) {
    topic_str += characters[topic[i]];
  }

  fill(0, 0, 0, TOPIC_FADE);
  rect(0, (TOPIC_Y / 2) + 5, width, TOPIC_SIZE);

  textSize(TOPIC_SIZE);
  fill(STATIC);
  textAlign(CENTER);
  text(topic_str, width/2, TOPIC_Y);
}

Notice how the topic is described by the position of each character of the text THE_MATRIX.

int[] topic = {50, 38, 35, 61, 43, 31, 50, 48, 39, 54};

When displaying, the text is reconstructed using the characters. This introduces the glitching effect to the topic too.

That is pretty much the guts of this demo. We also have two supporting functions.

void keyPressed() {
  if (key == PAUSE_KEY) {
    matrix = !matrix;
  }
}

Here, when the space bar is pressed, the matrix effect is paused; essentially stops spawning new streaks in the buffer. And vice versa if pressed again.

void mouseClicked() {
  int x = (mouseX * COLUMN_COUNT) / width;
  int y = (mouseY * row_count) / height;

  fill(STATIC);
  rect(x * column_size, y * row_size, column_size, row_size, 5);

  buffer.add(new Streak(x, y));
}

Also, when the mouse is clicked on the screen, using the x and y coordinates of the cursor, a new streak is created in that specific location.

Matrix animation

Combined, this simple code creates an amazing and sophisticated visual effect created for the movie screen. You can tweak some of the parameters to see how that affects the animation.

External Links: