Color Landscape with Perlin Noise

I used an ASCII code for serial communication from Arduino to Processing, which changed the colors and speed of a Perlin noise animation.

Circuit:

IMG_3540

  • red cable connects power to 5V
  • green cables connect tilt sensors to power (green switches are side-to-side, white switches are up-and-down)
  • black cable connects ground to ground
  • 10 k resistors connect switches to ground
  • blue cables connect breadboard pins to Arduino pins:
    • breadboard pin 8 to digital pin 8 = Left (1,0)
    • breadboard pin 18 to digital pin 7 = Right (0,1)
    • breadboard pin 23 to digital pin 13 = Up (0, 1)
    • breadboard pin 27 to digital pin 12 = Down (1,0)

*testing the side-to-side values by commenting out the up and down values

3D Perlin noise:

Code Source: Processing 3D Noise example

*below the increment is changed to -.01 and a void mousePressed(){ } function added to change from greyscale to color

Link: https://processing.org/examples/noise3d.html

int x = 0;
int y = 0;
float increment = -.01;
//noise function argument #3 (a global variable that increments one per cycle)
float zoff = 0.0; //incremement zoff != xoff or yoff
float zincrement = 0.02;

void setup() {
size(640, 640);
background(0);
frameRate(30);
}

void draw() {
//adjust noise detail
noiseDetail(8, 0.65f);
loadPixels();
float xoff = 0.0; //start xoff at 0
//for every x, y coordinate in a 2D space, calculate a noise value
//and display a brightness value
for (int x = 0; x < width; x++) {
xoff += increment; //increment xoff
float yoff = 0.0; //for every xoff, start yoff at 0
for (int y = 0; y < height; y++) {
yoff += increment; // increment yoff
// calculate noise and scale by 255
float bright = noise(xoff, yoff, zoff)*255;
// set each pixel onscreen to a grayscale value
pixels[x+y*width] = color(bright, bright, bright);
}
}
updatePixels();
zoff += zincrement; // increment zoff
}
void mousePressed() {
fill(pixels[x+y*width] = color(255));
}

A few times people have described tinnitus to me as the sound that noise looks like on a TV. I found a code for 3D Perlin noise to generate an index of pixels on the screen that look like noise but can be manipulated to simulate textural gradients. Below is a diagram of 2D noise, showing the pixel coordinates in the algorithm, moving in the direction of the arrows. The arrows represent the xoff and yoff variables in the code, controlling the direction in which the pixels appear to randomly move in during the for() loop. The math explains the calculation for this randomness: the gradient is made up of vectors, so the code uses vector coordinates from within each cube (3D pixel) and a point on its edges.[1] The code generates a pseudorandom vector gradient, so the noise pixels seem to move randomly but actually move the same way according to the input integers.[2]

Screen Shot 2017-05-20 at 10.48.20 AM

image source: https://gamedev.stackexchange.com/questions/23625/how-do-you-generate-tileable-perlin-noise

[1] http://flafla2.github.io/2014/08/09/perlinnoise.html

[2] http://flafla2.github.io/2014/08/09/perlinnoise.html

The noiseDetail function changes the appearance by increasing the number of pixels and decreasing their size, so that the grid of pixels is denser. Because the increment functions, controlling the rate at which the pixels move, decreased the frameRate from 30 to a range between 5 and 10, I tried to change it as little as possible. Instead, I focused on color and sound because I couldn’t marble the texture without decreasing the frameRate. I planned to make a pixel that marbles the noise texture in the direction of the maze’s motion, signaled by the tilt switches on the on the bottom of the maze, but the cables kept coming out. Then I decided to keep the cloudy texture and make a changing noise landscape.

*above: this is the only video with sound; when I wanted to represent the player with the marble as a pixel, I used (pmouseX, pmouseY, mouseX, mouseY) to draw a line tracing the movement to change the noise from the library example PinkNoise along with the pixel index statement, color(1*mouseX/1.5,bright,(mouseY/1.5)*255), to change with the sound amplification.

*below: I drew a rectangle with the mouse because I didn’t like the idea of representing the player, so I considered drawing its perspective in the maze by connecting lines from the corners of each angle to the corners of the screen. I decided to just work on the animation as a separate project; my ideas for the maze and the noise seemed better off separate.

I tested the code with boolean statements, then replaced them with if ( ){} / }else if( ){ function for the serial communication. With the if/else function for the side-to-side tilt, the left switch changed the screen from colorful noise, indicated by println(“flat”) in Processing and (0 , 0) from the Arduino, to saturated hues; separately, the if/else statement for the right tilt switched the screen from the same colorful noise – showing that the board was not tilted – to a more striated, and consequently more slowly moving, yellow screen. I multiplied the yoff increment by 5 to stretch the bands of color sideways, across the x-axis.

Screen Shot 2017-05-20 at 2.10.20 AM

*noise with the xoff increment increased

I worked on making layers with different variables and using the map function, but both decreased the frameRate. In the version that I presented, the color mode changes from HSB for the left tilt and to RGB for the right so that the color shifts from saturated while tilted left, to grainy while flat, and to RGB color while tilted right. I added a Perlin noise wave also to cover the screen where the up tilt switch meets the side to side, so that the noise wave would appear like a wavey landscape whenever the tilt switch pointed down. Processing crashed before I saved my most recent edit, which changed several of the vertices so that the wave was wider at the edges of the screen.

Source for 2D Noise Wave: https://processing.org/examples/noisewave.html

*original code in Processing examples

float yoff = 0.0;       // 2nd dimension of perlin noise

void setup() {

size(640, 360); }

void draw() {

background(51);

fill(255);   // We are going to draw a polygon out of the wave points

beginShape();

float xoff = 0;       // Option #1: 2D Noise

// float xoff = yoff; // Option #2: 1D Noise

// Iterate over horizontal pixels   for (float x = 0; x <= width; x += 10) {

// Calculate a y value according to noise, map to

float y = map(noise(xoff, yoff), 0, 1, 200,300); // Option #1: 2D Noise

// float y = map(noise(xoff), 0, 1, 200,300);   // Option #2: 1D Noise

// Set the vertex

vertex(x, y);

// Increment x dimension for noise

xoff += 0.05;   }

// increment y dimension for noise

yoff += 0.01;

vertex(width, height);

vertex(0, height);

endShape(CLOSE); }

To improve this project, I would use different sensors to shift the colors more smoothly. I think that analog would work better for this project than digital, so that the colors would change with a range of values up to 255 instead.

ARDUINO tilt sensor:

int buttonPinL = 7;    //button pin left
int buttonPinR = 8;   //button pin right

int buttonPinU = 13;  //button pin up
int buttonPinD = 12;  //button pin down


void setup() {
  // put your setup code here, to run once:
  pinMode(buttonPinL, INPUT);
  pinMode(buttonPinR, INPUT);
  pinMode(buttonPinU, INPUT);
  pinMode(buttonPinD, INPUT);

  Serial.begin(9600); //baud rate

}

void loop() {

  //LEFT RIGHT 
int valueL = digitalRead(buttonPinL);
Serial.print(valueL);                     //read serial value L (1/0)
Serial.print(",");              //separate the values

//update value
int valueR = digitalRead(buttonPinR);    //read serial value R (0/1)
Serial.print(valueR);
Serial.print(",");              //separate the values

  //UP DOWN
int valueU = digitalRead(buttonPinU);
Serial.print(valueU);                       //read serial value U (0/1)
Serial.print(",");                  //separate the values

//update value
int valueD = digitalRead(buttonPinD);       //read serial value D (1/0)
Serial.print(valueD);

Serial.println();
delay(10); //delay 10 milliseconds

}

PROCESSING:

import processing.serial.*;
String myString;    //0/1 values in Arduino
Serial myPort;


//SERIAL COMMUNICATION FROM ARDUINO
int tiltL;     //declare variables for left and right
int tiltR;
int tiltU;    //declare variables for up and down
int tiltD;

//PERLIN NOISE
int x = 0;
int y = 0;
int Wx = 0; //
int Wy = 0;

float xoff;     //for manipulation later
float yoff;
float increment = .01;
float xincrement = .02;    //variable for x increment
//noise function argument #3 (a global variable that increments one per cycle)
float zoff = 0.0; //incremement zoff != xoff or yoff
float zincrement = 0.02;
float bright;
float Wyoff = 0.0; //

void setup() {
  background(0);
  size(640, 640);
  frameRate(30);     //30sec new random location for pixels

  printArray(Serial.list()); //list serial devices
  myPort = new Serial(this, Serial.list()[1], 9600); //new port, common data rate
  //clear out the buffer of the port
  myPort.clear();
}

void draw() {
  while (myPort.available() > 0) { //available function (not a variable)
    //put what is in my port into the string
    myString = myPort.readStringUntil(10); //10 ASCII = Serial.println
    //    println(myString);
    //condition to test whether my string is null
    if (myString != null) {
      //      println(myString);
      //split and trim data from Arduino
      String[] data = split(trim(myString), ","); //data from Arduino, split by commas
      //use a loop to print data
      //      print(data);
      for (int i = 0; i < data.length; i++) { //initial value, length of data array
        print(data[i]);
        print(",");
      }
      tiltL = int(data[0]); //turn data into an integer
      tiltR = int(data[1]);  
     tiltU = int(data[3]);
     tiltD = int(data[4]);
      println();
    }
  }
//LEFT TILT saturated color
  if (tiltL == 1) { 
   colorMode(HSB);
    noiseDetail(8, 0.65f);
    loadPixels();
    float xoff = 0.0; //start xoff at 0
    for (int x = 0; x < width; x++) {
      xoff += increment;               //increment xoff
      float yoff = 0.0;                 //for every xoff, start yoff at 0
      for (int y = 0; y < height; y++) {
        yoff += increment;               // increment yoff
        float bright = noise(xoff, yoff, zoff)*255;
        // set each pixel onscreen to a grayscale value
        pixels[x+y*width] = color(bright, bright, 255);
      }
    }
    updatePixels();
    zoff += zincrement; // increment zoff
     println("left");
 //FADE SATURATION INTO STATIC
  } else if (tiltL == 0) { 
       colorMode(RGB);
    noiseDetail(8, 0.65f);
    loadPixels();
    float xoff = 0.0; //start xoff at 0
    for (int x = 0; x < width; x++) {
      xoff += increment;               //increment xoff
      float yoff = 0.0;                 //for every xoff, start yoff at 0
      for (int y = 0; y < height; y++) {
        yoff += increment;               // increment yoff
        float bright = noise(xoff, yoff, zoff)*255;
        float hue = noise(xoff, yoff, bright)*255;
        // set each pixel onscreen to a grayscale value
        pixels[x+y*width] = color(bright, hue, 255);
      }
    }
    updatePixels();
    zoff += zincrement; // increment zoff
      println("flat");
  }
//FADE STATIC INTO COLOR
    if (tiltR == 0) { 
    noiseDetail(8, 0.65f);
    loadPixels();
    float xoff = 0.0; //start xoff at 0
    for (int x = 0; x < width; x++) {
      xoff += increment;               //increment xoff
      float yoff = 0.0;                 //for every xoff, start yoff at 0
      for (int y = 0; y < height; y++) {
        yoff += increment;               // increment yoff
        float bright = noise(xoff, yoff, zoff)*255;
        float hue = noise(xoff, yoff, bright)*255;
        pixels[x+y*width] = color(bright, hue, 255);
      }
    }
    updatePixels();
    zoff += zincrement; // increment zoff
      println("flat");
 //RIGHT TILT RGB color
  } else if (tiltR == 1) {
  noiseDetail(8, 0.65f);
  loadPixels();
  float xoff = 0.0; //start xoff at 0
  for (int x = 0; x < width; x++) {
    xoff += increment;               //increment xoff
    float yoff = 0.0;                 //for every xoff, start yoff at 0
    for (int y = 0; y < height; y++) {
      yoff += increment;               // increment yoff
  float bright = noise(xoff, yoff, zoff)*255;
    pixels[x+y*width] = color(bright, 255, 139);
  }
    }
updatePixels();
zoff += zincrement; // increment zoff
  println("right");
}

//draw sky
if(tiltU == 1){
    noiseDetail(8, 0.65f);
  loadPixels();
  float xoff = 0.0; //start xoff at 0
  for (int x = 1; x < width; x++) {
    xoff += increment;               //increment xoff
    float yoff = 0.0;                 //for every xoff, start yoff at 0
    for (int y = 1; y < height/3; y++) {
      yoff += 2.5*increment;               // increment yoff
  float bright = noise(xoff, yoff, zoff)*255;
 pixels[x+y*width] = color(135, 150, bright);
  }
  }
  updatePixels();
  zoff += zincrement; // increment zoff
  println("up");
}else if(tiltU == 0){
    noiseDetail(8, 0.65f);
  loadPixels();
  float xoff = 0.0; //start xoff at 0
  for (int x = 0; x < width; x++) {
    xoff += increment;               //increment xoff
    float yoff = 0.0;                 //for every xoff, start yoff at 0
    for (int y = 0; y < height/3; y++) {
      yoff += 5*increment;               // increment yoff
  float bright = noise(xoff, yoff, zoff)*255;
 pixels[x+y*width] = color(255, 255, bright);
  }
  }
  updatePixels();
  zoff += zincrement; // increment zoff
println("level");
}
//draw noise wave as landscape
if(tiltD == 1){
  fill(255);
  beginShape();
  float Wxoff = 0;
  for (float Wx = 0; Wx <= width; Wx += 10) {
    float Wy = map(noise(Wxoff, Wyoff), 0, 1, 200,300);
    vertex(Wx, Wy);
    Wxoff += 0.05;
  }
  Wyoff += 0.01;
  vertex(width, height/2);
  vertex(0, height/2);
  endShape(CLOSE);
  println("down");
}else if(tiltD == 0){
println("level");
}
}

Leave a Reply