Flow field generator in P5js

This is a further exploration of Daniel Shiffman visualization of a flow field. Using perlin noise and vector math in P5js.

We’ll start with a grid across the canvas

var scl = 20;
var cols;
var rows;

function setup() {
createCanvas(windowWidth,windowHeight);
cols = floor(width/scl);
rows = floor(height/scl);
}

function draw() {
background(255);
stroke(255);
noFill();
beginShape();
for(var y=0; y<rows;y++){
xoff =0;
for(var x=0; x<cols;x++){
var index = (x+y*width);
stroke(0);
push();
translate(x*scl,y*scl);
pop();
rect(x*scl, y*scl,scl,scl);
}
}
}

We’ll create a couple of increment variables”xoff” and “yoff”. These will increment with a low coefficient on each loop so we can create a two dimensional perlin noise variable called “angle”. We’ll create a vector with that angle using the p5.Vector.fromAngle() function, and draw lines instead of the grid at their corresponding x and y.

var inc = 0.1;
var scl = 20;
var cols;
var rows;
var yoff;
var xoff;

function setup() {
createCanvas(windowWidth,windowHeight);
cols = floor(width/scl);
rows = floor(height/scl);
}

function draw() {
background(255);
stroke(255);
noFill();
beginShape();
yoff =0;
for(var y=0; y<rows;y++){
xoff =0;
for(var x=0; x<cols;x++){
var index = (x+y*width);
var angle = noise(xoff,yoff)* TWO_PI;
var v = p5.Vector.fromAngle(angle);
stroke(0);
push();
translate(x*scl,y*scl);
rotate(angle);
line(0,0,scl,0);
pop();
}
}
}

We’ll increment the xoff +=inc; and yoff +=inc;

Let’s add a z dimension into the perlin noise angle variable and also store all

var inc = 0.1;
var scl = 20;
var cols;
var rows;
var zoff = 0;

function setup() {
createCanvas(windowWidth,windowHeight);
cols = floor(width/scl);
rows = floor(height/scl);
}

function draw() {
background(255);
beginShape();
var yoff =0;
for(var y=0; y<rows;y++){
xoff =0;
for(var x=0; x<cols;x++){
var index = x+y*cols;
var angle = noise(xoff,yoff,zoff)* TWO_PI;
var v = p5.Vector.fromAngle(angle);
stroke(0);
push();
translate(x*scl,y*scl);
rotate(angle);
strokeWeight(1);
line(0,0,scl,0);
pop();
xoff +=inc;
zoff +=0.00001;
}
yoff +=inc;
}

We’ll add a particles object and we’ll try to make sure the angle force gets applied to them. It will look as if the particles are following the nearest vector path.

var inc = 0.1;
var scl = 20;
var cols;
var rows;
var zoff = 0;
var particles = [];
var flowField;

function setup() {
createCanvas(600,600);
cols = floor(width/scl);
rows = floor(height/scl);
flowField = new Array(cols*rows);//store all of the coordinates of the flowfield
for(var i=0; i<100;i++){
particles[i] = new Particle();//start object
}
}

function draw() {
background(255);
beginShape();
var yoff =0;
for(var y=0; y<rows;y++){
xoff =0;
for(var x=0; x<cols;x++){
var index = x+y*cols;//position of each field
var angle = noise(xoff,yoff,zoff)* TWO_PI;
var v = p5.Vector.fromAngle(angle);
v.setMag(0.1);//normalize this vector
flowField[index] = v;//store all of the vectors calculated into flowField
stroke(0);
push();
translate(x*scl,y*scl);
rotate(angle);
strokeWeight(1);
line(0,0,scl,0);
pop();
xoff +=inc;
zoff +=0.00001;
}
yoff +=inc;
}
//addParticles();
for(var i=0; i<particles.length;i++){
particles[i].follow(flowField)
particles[i].show();
particles[i].update();
particles[i].edges();
}
}


//PARTICLES OBJECT CONSTRUCTOR
function Particle(){
this.pos = createVector(random(width),random(height));
this.vel= createVector(0,0);
this.acc= createVector(0,0);
this.update = function(){
this.vel.add(this.acc);
this.pos.add(this.vel);
this.acc.mult(0);//Reset acceleration
}
this.applyForce = function(force){
this.acc.add(force);
}
this.show = function(){
strokeWeight(4);
stroke(2);
point(this.pos.x,this.pos.y);//let's make some points
}
this.edges = function(){
if(this.pos.x>width)this.pos.x = 0;
if(this.pos.x<0)this.pos.x = width;
if(this.pos.y<0)this.pos.y = height; if(this.pos.y>height)this.pos.y = 0;
}//keep the particles inside the canvas
this.follow = function(vectors){
var x = floor(this.pos.x/scl);//position in relationship to scale "vectr unit or grid"
var y = floor(this.pos.y/scl);
var index = x+y * cols;
var force = vectors[index];
this.applyForce(force);
}
}

We’ll fix the speed of the particles with a var called maxspeed, and add a function that follows the path of the flow field vectors. We’ll draw some lines to follow this path.

var inc = 0.1;
var scl = 20;
var cols;
var rows;
var zoff = 0;
var particleObejct = 7000;
var particles = [];
var flowField;
function setup() {
background(255);
createCanvas(windowWidth,windowHeight);
cols = floor(width/scl);
rows = floor(height/scl);
flowField = new Array(cols*rows);
for(var i=0; i<particleObejct;i++){
particles[i] = new Particle();
}
}
function draw() {
beginShape();
var yoff =0;
for(var y=0; y<rows;y++){
xoff =0;
for(var x=0; x<cols;x++){
var index = x+y*cols;
var angle = noise(xoff,yoff,zoff)* TWO_PI;
var v = p5.Vector.fromAngle(angle);
v.setMag(1);
flowField[index] = v;//store all of the vectors calculated into flow field
//push();
//translate(x*scl,y*scl)
//rotate(angle);
//strokeWeight(1);
//stroke(0,5);
//line(0,0,scl,0);
//pop();
xoff +=inc;
}
yoff +=inc;
}
for(var i=0; i<particles.length;i++){
particles[i].follow(flowField);
particles[i].edges();
particles[i].show();
particles[i].update();
}
}
function Particle(){
this.pos = createVector(random(width),random(height));
this.vel= createVector(0,0);
this.acc= createVector(0,0);
this.maxspeed = 10;
this.prePos = this.pos.copy();
this.update = function(){
this.vel.add(this.acc);
this.vel.limit(this.maxspeed);
this.pos.add(this.vel); this.acc.mult(0);
}
this.applyForce = function(force){
this.acc.add(force);
}
this.show = function(){
stroke(0,20); strokeWeight(1); line(this.pos.x,this.pos.y,this.prePos.x,this.prePos.y);
this.updatePrev();
}
this.updatePrev = function(){
this.prePos.x = this.pos.x; this.prePos.y = this.pos.y;
}
this.edges = function(){
if(this.pos.x>width){
this.pos.x = 0;
this.updatePrev();
}
if(this.pos.x<0){
this.pos.x = width;
this.updatePrev();
}
if(this.pos.y<0){ this.pos.y = height; this.updatePrev(); } if(this.pos.y>height){
this.pos.y = 0;
this.updatePrev();
}
}
this.follow = function(vectors){
var x = floor(this.pos.x/scl);//position in relationship to scale "vector" unit or grid"
var y = floor(this.pos.y/scl);
var index = x+y * cols;
var force = vectors[index];
this.applyForce(force);
}
}