Vector fields in Processing

Vladislav Lukashevich
6 min readSep 2, 2019

--

In this article I want to program vector fields in Processing. If you are not familiar with vector fields, I suggest you watch this video from Khan Academy:

But in short, vector field is a way to visualize multivariable vector-valued functions. We give them several variables as an input and instead of a single number (like scalar valued functions) they return us a whole vector. Vectors have both magnitude (or length) and direction. We can think of their direction as an angle of rotation. Usually, vectors are represented with arrows. Vectors and vector fields can be in as many dimensions as we want, but now we will focus on two-dimensional vector fields.

First of all, we need a mathematical function that we will give us a 2d vector field. For example:

We give this function a point on the coordinate plane and it returns us a vector. Now, if we draw all vectors at the origin it looks very crammed. So, we translate output vector to the input point. However, if we draw them in full length it still might look jam-packed. There are a couple solutions to this problem. First, we can just ignore the magnitude and draw vectors with a fixed length. But then we lose a feeling of their magnitude completely. Second, we can use color. Lastly, we can scale down all vectors so that maximum magnitude is always the same length for all fields. This image from Wolfram alpha shows how the vector field of our example function looks like:

We should think of coordinate plane as a grid and vectors will be assigned to each square. Let’s start coding now.

Actually, we don’t need a lot of global variables:

  • len is a length of a side of each square;
  • cols and rows are the numbers of columns and rows on the grid;
  • grid is a 2d array of Cell objects that represent each square on the grid.

First, I write a vector field function. Its type is PVector and it receives x and y coordinates of each square. Then I calculate components of a vector: u is x-component and v is y-component. Finally, I just return this vector.

PVector vector__field (float x, float y) {
x = map(x, 0, width, -3, 3);
y = map(y, 0, height, -3, 3);

float u = x;
float v = y;

return new PVector(u, v);
}

You may think that it is strange that we calculate u and v because u is equal to x and y is equal to v and we can pass them to PVector constructor directly. But it is the case only for our example function. If the formula is different, these components are not equal and it makes sense to calculate them separately.

Now, let’s make the Cell object. It needs two indexes i and j that represent its position in 2d array that I called grid and vector associated with a vector field. I will pass only indexes to the constructor function. Then I will calculate x and y coordinates of the center of a cell and a corresponding vector. Also you can make an argument and magnitude separate variables.

class Cell {
int i, j;
PVector vec;
float arg;
float mag;
Cell (int _i, int _j) {
i = _i;
j = _j;

float x = (i + 0.5) * len;
float y = (j + 0.5) * len;

vec = vector__field(x, y);
mag = vec.mag();
arg = vec.heading();
}
}

Then, I create a show function that will draw an arrow corresponding to an associated vector. First of all, I check if the magnitude is 0. If it is, there is essentially no vector and we cannot draw anything. And then, I draw an arrow and rotate it by an argument of a vector. Drawing an arrow may sound simple, but it is actually a little bit tricky. Now, let me explain how we can do that.

Let l be a length of an arrow and r be the length of two smaller legs.

First, we translate to the center of a cell. So, (0, 0) is now its center. We draw an arrow so that its center is at (0, 0). Then, it rear points are (-l/2 0) and (l/2, 0).

Smaller legs are rotated relatively to the arrow. I call the angle of rotation theta. Now, we use a bit of trigonometry to calculate coordinates of their ends. We can note that their x-coordinates are the same and their y-coordinates have the same absolute value but opposite signs. Now we can derive a simple system of equations in polar coordinates. Here is my sketch to make it clear.

So, I calculate these points and draw lines. And before that, I rotate everything by the argument of an angle. But it is important to use push() and pop() functions to apply rotation to only one particular arrow. I also added a buffer to make arrows a little bit shorter and not overlap with other arrows. Code should look like this:

void show () {
if (mag != 0) {
push();
strokeWeight(2);
stroke(0);
float r = 8;
float l = len;
float buffer = 5;
translate((i + 0.5) * l, (j + 0.5) * l);
rotate(arg);
line(-l/2 + buffer, 0, l/2 - buffer, 0);

translate(l/2 - buffer, 0);
float a = radians(150);
float x1 = scl * r * cos(a);
float y1 = scl * r * sin(a);

line(0, 0, x1, y1);
line(0, 0, x1, -y1);

pop();
}
}

Now there is only a tiny step left. We need to make a setup function, there we will draw background, initiate all variables, and draw all vectors.

void setup () {
size(630, 630);
background(255);

cols = floor(width / len);
rows = floor(height / len);

grid = new Cell[cols][rows];

for (int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
grid[i][j] = new Cell(i, j);
grid[i][j].show();
}
}
}

Now, let’s look at the results of our program and try different fields!

As you can see, I made all vectors the same length. I leave scaling and coloring as an exercise for you. You can check out my code on Github: https://github.com/vlukashevich/vector-fileds-processing

--

--