Friday, 2 December 2016

Visualising Mathematics With Processing.

Visualising Mathematics In Processing.

Technical Blog 2.

https://processing.org/exhibition/
In my previous technical post I introduced the programming language Processing and highlighted some basic features. This time, we will be looking at some more advanced Processing concepts and techniques before using these ideas to do the very thing which got me interested in Processing (and programming in general).

That thing is: visualising mathematics. 


2D Random Walk

A 2D Random Walk
The very first Processing sketch I ever saw was a model of a random process. Here we are also going to model a simple random process using Processing's random() function. We have already seen this function in action in the very last example in my previous post where random() was used to generate a randomly sized circle with a random colour.

This time we are going to use random() to simulate the flipping of a coin and increment a variable by +1 or -1 based on the result. To do this in Processing we will first select a random number between 0 and 2, then if the number if less then 1 we will decrement our variable, if the number is greater than 1 we will increment our variable.

We will do this twice, once for our x values and once for our y values, and draw a square at the coordinates (x,y). Doing this each frame will give us a random walk.

However, it would be nice to see the path that our square is tracing out - so here we can implement a cheap Processing trick and instead of drawing the background again every frame, we can instead draw a rectangle which is the size of the whole canvas but slightly transparent!

This will give the effect of our square tracing out a path which get's fainter over time. The full code is below:
int x = 0;
int y = 0;
int step = 5;
int side = 10;
 
void setup() {
  size(400, 400);
  background(255);
  frameRate(20);
}
 
void draw() {
  //draw a transparent background
  noStroke();
  fill(255, 5);
  rect(0, 0, width, height);

  //draw our randomly moving square
  noStroke();
  fill(0);
  rect(width/2 + x, height / 2 + y, side, side);

  //flip two coins
  float xinc = random(0, 2);
  float yinc = random(0, 2);

  //increment or decrement x and y
  if (xinc < 1) {
    x -= step;
  } else {
    x += step;
  }
  if (yinc < 1) {
    y -= step;
  } else {
    y += step;
  }
 
  //these two functions stop our square
  //from going off the screen.
  x = constrain(x, - width / 2, width / 2);
  y = constrain(y, - height / 2, height / 2);
}
Experiment with the step size and the size of the square, you could even try changing the square to an ellipse. You could also try colouring the square as it moves around the canvas either by linking the colour to the position or to the number of frames that have passed (using the HSB colour mode and the in built frameCount variable for example).

We will see later on a more elegant way of tracing a path without drawing a transparent rectangle over our whole sketch.

2D Fractal

For the fractal geometry course I took during my degree I had to write a program to display the fractal you can see below:
A fractal I had to draw during my degree.
So here I am going to walk you through the code which created that image.

Firstly we need to understand how the fractal is created. The fractal is defined by an iterated function system (IFS), in particular using four contraction mappings to define each of the four stems protruding from the centre of the fractal. [1]

The fractal is built up in a number of stages. Firstly a cross is drawn, then in the centre of each branch another cross is drawn, but scaled down. Then in the middle of each of those new branches another cross is drawn, and so on.

To do this in Processing we can use recursion.

First we need to write a function to draw a cross. We can define functions in Processing just like we would write methods in Java - but we don't need to worry about access modifiers or any other wrappers just type void myFunction(...)and write your function (exchanging void for whatever your return type is of course. Although in Processing we often write functions to draw things - so they have a return type of void). Drawing the cross itself is very easy using Processing's line(...) function. It will be helpful to use a single parameter to define how big the cross is.

With the cross, we need to write another function to recursively draw a smaller cross at the middle of each branch of the large cross. To do this we will make use of Processing's translate(..) function.


The Functions pushMatrix(), popMatrix() and translate(...)

Remember from the last post where we learned that the point (0,0) in a Processing sketch is the top left corner of the window. Isn't that kind of annoying? Wouldn't you rather have (0,0) be in the middle of the screen? Fortunately you can! Try the following code:
void setup() {
  size(400, 400);
  background(255);
  noStroke();
  fill(255, 0, 0); //red
  ellipse(0, 0, 150, 150);
  translate(width/2, height/2);
  fill(0, 0, 255); //blue
  ellipse(0, 0, 150, 150);
}
Notice that both circles were drawn at (0,0) but the blue circle is in the middle of the screen! This is because we translated to (width/2, height/2) before drawing the circle.

Any transformation of the canvas overwrites the previous state unless you use the pushMatrix() function to save the current state. This function pushes the current transformation matrix onto the stack. To revert to the previous state we can use popMatrix() to pop the current transformation off the top of the stack and revert to the previous state.

We will use the translate function to draw our crosses at different locations on the screen and pushMatrix() and popMatrix() to control the translations.


Back To The Fractal...

Now that we can translate our crosses round the canvas at will, we need to write a recursive function that will draw the crosses where we need them to be.

Our function will need a parameter to control the size of the crosses it draws. Inside our function we will need to scale the cross down to the appropriate size and translate it to the middle of one of the branches before calling the function again to repeat the process on the smaller cross. We will need to do this four times - once for each branch.

The full code is below:
float s = pow(2, 10); //initial size of the cross 
void setup(){
  size(1080, 600);
  background(255);
  noLoop(); //stops draw() from looping
}

void draw(){
  translate(width/2, height/2);
  fractal(s);
                        //uncomment the below line to save a picture!
  //save("Cross_Fractal.png");
}

//recursive function to draw the fractal
void fractal(float sc){   
  sc = sc/2;
          //stop when too small
  if (sc>4){         
    
        //draws the left horizontal line
    pushMatrix(); 
    cross(sc);
    translate(-sc/2, 0);
    fractal(sc);
    popMatrix();
    
        //draws the right horizontal line
    pushMatrix();       
    cross(sc);
    translate(sc/2, 0);
    fractal(sc);
    popMatrix();
    
        //draws the bottom vertical line
    pushMatrix();       
    rotate(-PI/2);
    translate(-sc/4, 0);
    cross(sc/4);
    fractal(sc/2);
    popMatrix();
    
        //draws the top vertical line
    pushMatrix();       
    rotate(-PI/2);
    translate(sc/4, 0);
    cross(sc/4);
    fractal(sc/2);
    popMatrix();
  }
}

//draws our cross
void cross(float len){    
  stroke(0);
  line(-len, 0, len, 0);
  line(0, -len/2, 0, len/2);
}
Notice how the fractal(...) function makes use of pushMatrix(), translate(...) and popMatrix() to draw the cross at the required location on the canvas.

We stop draw() from looping by calling noLoop() in setup() because we just want a still image, so we don't need to redraw it 60 times a second. There is also an option to save a png image by uncommenting the line with the save(...) function.

Now it is time to move on to the world of 3D graphics in Processing.

Interactive 3D Lorenz Attractor

We are going to create a simulation of the Lorenz Attractor in Processing.
Lorenz Attractor
The Lorenz Attractor is a very famous fractal coming from the world of chaos theory. In the 1960's Edward Lorenz was trying to model atmospheric convection and developed the following system of differential equations: [2]


{\begin{aligned}{\frac {\mathrm {d} x}{\mathrm {d} t}}&=\sigma (y-x),\\{\frac {\mathrm {d} y}{\mathrm {d} t}}&=x(\rho -z)-y,\\{\frac {\mathrm {d} z}{\mathrm {d} t}}&=xy-\beta z.\end{aligned}}
Lorenz System. /wiki/Lorenz_System

The solution of this system turns out to be a strange attractor [3].

The code itself is adapted from Daniel Shiffman's video tutorial on the Lorenz Attractor, where he walks through the algorithm itself. However, there are some concepts we need to discuss before we can fully understand the code. [4]


3D Graphics and the PeasyCam Library

Processing makes 3D graphics very easy. Simply include "P3D" as a third parameter when declaring the size of your window, e.g. size(800, 600, P3D). We can then create 3D shapes, such as spheres and boxes using Processing's in build shape functions. However, 3D shapes are always drawn at (0, 0, 0) so we need to translate to the point in 3D space where we want to draw the shape, rather than declaring where we want the shape to be in the shape's constructor.

The following code serves as a demonstration of 3D graphics in Processing:
float r = 0;
void setup(){
  size(400, 400, P3D);
  background(0);
} 
void draw(){
  lights();
  background(0);
  translate(width/2, height/2, 50);
  rotateX(radians(r));
  rotateY(radians(r));
  noStroke();
  fill(255);
  box(100);
  r+=0.5;
}
This code also makes use of the rotate functions, which rotate the sketch about whichever axis you choose. The lights() function adds some basic lighting to make 3D objects look a little nicer. Without the rotate or lights, you will just see a white square on the screen.

As nice as the above sketch is, it would be much better if we could interact with our 3D world, rather than just watching it rotate. For that, we need to use the PeasyCam library. [5]

To install PeasyCam, go to Sketch > Import Library... > Add Libary... and search for PeasyCam. Select PeasyCam and click install and then you are ready to make interactive 3D sketches.

To use PeasyCam simply type import peasy.*; at the top of your sketch. Then we need to declare a new PeasyCam object, so on the next line type PeasyCam camera; finally inside setup we create the camera by typing camera = new PeasyCam(this, 0, 0, 0, 500);. The parameters define which sketch to add the camera to, where the camera should be focused (PeasyCam automatically translates everything to (width/2, height/2, 0) so (0, 0, 0) in the PeasyCam constructor is actually the centre of our sketch) and the final parameter is how far away from our focal point the camera lens should be.

Try using PeasyCam in the above example by deleting the translate and rotate lines and declaring a PeasyCam object (notice how the camera movement does not affect the lighting!).


Using ArrayLists and PVector Objects to Create Custom Shapes

In the first example in this post we simulated a random walk and drew the trial by using a transparent background. As I mentioned, that is a cheap Processing trick and will not work for more complex examples (such as the Lorenz Attractor to come, or any other 3D sketch). So we are going to have to be a bit more intelligent and make use of some more Processing features.

 A better way of achieving the desired effect from before (and allowing more flexibility) is to store the positions of our shapes in PVector objects, and store those objects in an ArrayList which we can loop through every frame to draw the entire shape.

A PVector in Processing is nothing more than a 2- or 3-tuple of floats which can be thought of as a point in 2D or 3D space (or as a direction in 2D or 3D space). We can create a PVector by writing PVector p = new PVector(100, 150, 50); the values stored in our PVector can be accessed using the attributes p.x, p.y and p.z so we can translate to the point our PVector is storing by using translate(p.x, p.y, p.z);.

So instead of simply drawing a square at the random point be created each frame in our first sketch earlier, we can store the values in PVector form and then push the PVector to an ArrayList of PVectors.

We are then able to loop through the whole ArrayList each frame, and draw every point.

But wouldn't it be nice if we could join up all of our points into a continuous line? Well Processing makes that very easy by allowing us to define custom shapes. The below code is an example of defining a custom shape:
beginShape();
vertex(100, 100);
vertex(200, 100);
vertex(200, 200);
vertex(100, 100);
endShape();
This will draw three sides of a square. We connect the first and last vertices by specifying CLOSE as a parameter in endShape. Instead of listing out each vertex, we can use the ArrayList method just mentioned and loop through our ArrayList of PVectors defining a vertex for each PVector.

The following code is a development of out 2D random walk sketch to a 3D random walk using PeasyCam and the PVector ArrayList technique just described.
import peasy.*;
PeasyCam camera;

int x = 0;
int y = 0;
int z = 0;
int step = 5;
int side = 5;
ArrayList<PVector> points = new ArrayList<PVector>();

void setup() {
  size(400, 400, P3D);
  background(255);
  frameRate(20);
  camera = new PeasyCam(this, 0, 0, 0, 200);

void draw() {
  background(255);
  
  //draw whole path
  stroke(0);
  noFill();
  beginShape();
  for (PVector p : points){
    vertex(p.x, p.y, p.z);
  }
  endShape();
  
  //draw box at end of path
  pushMatrix();
  translate(x, y, z);
  noStroke();
  fill(0);
  box(side);
  popMatrix();

  //flip three coins
  float xinc = random(0, 2);
  float yinc = random(0, 2);
  float zinc = random(0, 2);

  //increment or decrement x and y
  if (xinc < 1) {
    x -= step;
  } else {
    x += step;
  }
  if (yinc < 1) {
    y -= step;
  } else {
    y += step;
  }
  if (zinc < 1) {
    z -= step;
  } else {
    z += step;
  }

  //add new point to list
  points.add(new PVector(x, y, z));
}
Leave this code running for a while and you'll get a rather intricate fractal like pattern in 3D space. As we have access to every vertex, we can also animate our shape and colour the shape more precisely (as we will do with the Lorenz Attractor code). I highly recommend experimenting with these techniques. 
Example 3D Random Walk

The Lorenz Attractor Itself

We are not ready to create our interactive Lorenz Attractor. As I mentioned before, you can watch Daniel Shiffman's video tutorial to see how the below code is constructed, although the code here includes the PeasyCam library to better explore the sketch.
import peasy.*; 
float xmag, ymag = 0;
float newXmag, newYmag = 0;
PeasyCam camera;
float x, y, z, dx, dy, dz;
float t, dt;
float sigma, beta, rho;
 
//this will store the whole attractor
//as a list of vertices
ArrayList<PVector> points = new ArrayList<PVector>();
 
void setup() {
  size(800, 600, P3D);
  smooth();
  camera = new PeasyCam(this, 0, 0, 0, 500);

  //set initial values
  dt = 0.01;
  x = 1;
  y = 1;
  z = 1;
  //set parameters
  rho = 28;
  sigma = 10;
  beta = 8/3;
}
 
void draw() {
  colorMode(RGB);
  background(0);
  scale(3);
  noFill();
  stroke(255);
 
  //calculate next step
  dx = sigma*(y-x)*dt;
  dy = (x*(rho-z)-y)*dt;
  dz = (x*y-beta*z)*dt;
 
  //add to current position
  x = x + dx;
  y = y + dy;
  z = z + dz;

  //update vertex list
  points.add(new PVector(x, y, z));
 
  //draw the whole attractor
  float hu = 0;
  beginShape();
  for (PVector v : points) {
    colorMode(HSB);
    //rainbow colouring
    stroke(hu%360, 255, 255);
    vertex(v.x, v.y, v.z);
    hu += 0.1;
  }
  endShape();

  //mark the new position
  pushMatrix();
  translate(x, y, z);
  colorMode(RGB);
  stroke(255);
  fill(255);
  sphere(0.5);
  popMatrix();
}

If You Want More...

For more examples of this sort of thing, check out Daniel Shiffman's YouTube channel.

Next time we will be looking at making games in Processing, in particular using p5.js for making web-games.

References 

[1] https://en.wikipedia.org/wiki/Iterated_function_system
[2] https://en.wikipedia.org/wiki/Lorenz_system
[3] https://en.wikipedia.org/wiki/Attractor
[4] https://www.youtube.com/watch?v=f0lkz2gSsIk
[5] http://mrfeinberg.com/peasycam/

No comments:

Post a Comment