3

The goal is to have an illustration of a vector field with arrows (i.e. segments with chevron at the tip) at each point on a 2D or 3D grid with p5.js. The reason is that I see a lot of generative art using it, and the flow fields (?) look cool, so why not use them for the depiction of a physics vector field with or without motion.

I have no clue about p5.js, but a quick online search shows me that I can generate positional vectors that start at the upper left-hand corner, and lines (segments) joining the origin to their ending points or going from one positional vector to another:

function setup() {
  createCanvas(500, 500);
}

function draw() {
  background(0);
  
  let vec1 = createVector(100,100);
  let vec2 = createVector(30,150);
  

    //First vector
      strokeWeight(10);
      stroke(250,250,250);
      line(0,0,vec1.x,vec1.y);
    
    //Second vector
      strokeWeight(10);
      stroke(250,0,250);
      line(0,0,vec2.x,vec2.y);
      
    //Difference vector
      strokeWeight(5);
      stroke(0,250,250);
      line(vec1.x,vec1.y,vec2.x,vec2.y);
    }

Now I would like to add the arrowheads and make them beautiful. The ideal, long-term perfection would be the material in 3Blue1Brown:

enter image description here

JAP
  • 405
  • 2
  • 11

1 Answers1

2

I found an answer here, which allows to, for instance draw a vector between two points (or the ends of two positional vectors):

function setup() {
  createCanvas(500, 500);
}


function draw() {
  background(240);

  let v0 = createVector(250,250); //Beginning point at center canvas.
  let v1 = createVector(50,50);   //Ending point at the tip of this positional vec.

  drawArrow(v0, v1, 'red'); //Function that draws a red vector from vec0 to vec1.
}


// draw an arrow for a vector at a given base position
function drawArrow(base, vec, myColor) {
  stroke(myColor);
  strokeWeight(4);
  fill(myColor);
  translate(base.x, base.y); //Will transport the object line (below) to the tip of the positional vector v1
  line(0, 0, vec.x, vec.y);  //The line from the O to the tip of v1
  rotate(vec.heading()); //Rotates the following triangle the angle of v1
  let arrowSize = 7; // Determines size of the vector arrowhead (triangle).
  translate(vec.mag() - arrowSize, 0); //Will translate a triangle below by the modulus of v1
  triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
}

Or here a vector from the center of the canvas to the tip of the mouse:

function setup() {
  createCanvas(500, 500);
}


function draw() {
  background(240);

  let v0 = createVector(250,250);
  let v1 = createVector(mouseX - 250, mouseY - 250);

  drawArrow(v0, v1, 'red');
}


// draw an arrow for a vector at a given base position
function drawArrow(base, vec, myColor) {
  stroke(myColor);
  strokeWeight(4);
  fill(myColor);
  translate(base.x, base.y);
  line(0, 0, vec.x, vec.y);
  rotate(vec.heading());
  let arrowSize = 7;
  translate(vec.mag() - arrowSize, 0);
  triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
}

And here is not just one vector, but an actual (random) vector field created with the above code tweaking the lines here by Daniel Shiffman:

var inc = 0.1;
scl = 35;
var cols,rows;

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



function draw() {
  background(255);
  var yoff = 0;
  loadPixels();
  for (var y = 0; y < rows; y++) {
    var xoff = 0;
    for (var x = 0; x < cols; x++) {
      var index = (x + y * width)*4;
      var angle = noise(xoff,yoff) * TWO_PI;
      var v = p5.Vector.fromAngle(angle);
      xoff +- inc;
      fill('blue');
      stroke('blue');
      push();
      translate(x*scl,y*scl);
      rotate(v.heading());
      line(0,0,0.5*scl,0);
      let arrowSize = 7;
      translate(0.5*scl - arrowSize, 0);
      triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
      pop();
    }
    yoff += inc;
  }
}

enter image description here

Unfortunately, the origin at the upper-left hand corner renders physical fields mathematically expressed rather misleading, conveying the intuition the the field flows counter-clockwise:

var inc = 0.1;
scl = 35;
var cols,rows;

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



function draw() {
  background(255);
  var yoff = 0;
  loadPixels();
  for (var y = 0; y < rows; y++) {
    var xoff = 0;
    for (var x = 0; x < cols; x++) {
      var index = (x + y * width)*4;
      var angle = noise(xoff,yoff) * TWO_PI;
      //var v = createVector(sin(x)+cos(y),sin(x)*cos(y));
      var v = createVector(y,-x);
      xoff +- inc;
      fill('blue');
      stroke('blue');
      push();
      translate(x*scl,y*scl);
      rotate(v.heading());
      line(0,0,0.5*scl,0);
      let arrowSize = 7;
      translate(0.5*scl - arrowSize, 0);
      triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
      pop();
    }
    yoff += inc;
  }
}

enter image description here

The most common Cartesian coordinates with negative and positive values and four quadrants would show how this field is really flowing clockwise:

enter image description here

But this seems to have an almost simple fix by resetting the counters to range from minus the number of rows (and columns) to plus the number of rows (and columns), as opposed to starting from zero. In addition, there is a need to flip the direction of increasing values in the y axis, and the origin needs to be translated to the middle of the canvas (height/2,height/2) if square:

var inc = 0.1;
scl = 35;
var cols,rows;

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



function draw() {
  translate(height/2, height/2);  //moves the origin to bottom left
  scale(1, -1);  //flips the y values so y increases "up"
  background(255);
  var yoff = 0;
  loadPixels();
  for (var y = -rows; y < rows; y++) {
    var xoff = 0;
    for (var x =- cols; x < cols; x++) {
      var index = (x + y * width)*4;
      var angle = noise(xoff,yoff) * TWO_PI;
      //var v = createVector(sin(x)+cos(y),sin(x)*cos(y));
      var v = createVector(y,-x);
      xoff +- inc;
      fill('blue');
      stroke('blue');
      push();
      translate(x*scl,y*scl);
      rotate(v.heading());
      line(0,0,0.5*scl,0);
      let arrowSize = 7;
      translate(0.5*scl - arrowSize, 0);
      triangle(0, arrowSize / 2, 0, -arrowSize / 2, arrowSize, 0);
      pop();
    }
    yoff += inc;
  }
}

enter image description here

JAP
  • 405
  • 2
  • 11