Starting from a very simple path, let’s see how easy it can be to turn it into something interesting! The approach below is the basic method I used on my recent fx(hash) release, Crisis Worlds.

**Note:** The code samples use my own private framework. If you have some basic experience with JavaScript and p5js, it should be easy to adapt them. The variable `c`

represents my personal drawing library, and the methods should translate to p5js almost identically. I also use several random number convenience methods, and what they do should be clear from the function name.

First, we’ll create two starting points, an array to hold them, and then a function to draw them.

Below is the output.

We need to connect them with a line. We’ll modify the code in the `forEach`

loop to get the next point and draw a line between the current point and the one after it.

Let’s pull in some code from my last tutorial on generative brushes. But instead of drawing random circles at each step along the line, we’ll use it to build the array of points between the start and end.

Now we have a perfectly plain line between the start and endpoints. That’s no fun! Let’s add code to move each point around before we add it to the array.

You can go crazy with this, and if you do, you’ll see some interesting behavior soon.

To make things easier from here on, let’s extract the point drawing code into a new function.

What if we wanted to further refine and smooth out this line? We need an algorithm to take in all of our rough points and smooth out the corners by adding in new points. The Chaikin Algorithm is the perfect solution for this! Matt DesLauriers has a JavaScript implementation of it that we can use.

Adding the code to our example project and inlining the vec2-copy import, we get this.

Then we run it over our points array and get this. The smoothed line is in green.

Looking nice! What if we want even more smoothing? Just iterate the smoothing function a few times. Let’s modify the `chainkinSmooth`

function to iterate over the input points as many times as we specify.

Running this with 3 iterations returns a much smoother line.

And that’s it! We’ve gone from a simple line from point A to point B and transformed it into a flowing path. Experiment with different values, even polygons, to see what you can create! The more starting points you have, the wilder the paths will be.

Below are some examples with increasing amounts of starting points (`pointsToInsert`

).

Paths like this are perfect for tracing with a natural media brush 😉. I hope this has given you new ideas and new areas to explore in your work!

Please give me a follow on Twitter @nudoru and Instagram @hfaze! If you use this in a project, give me a shout out, I’d love to see what you make!

Below is the full code for what I’ve created above. As stated above, this is for my own framework and will need to be adapted to p5js.

```
const chaikinSmooth = (points, itr = 1) => {
const smoothFn = (input, output = []) => {
const copy = (out, a) => {
out[0] = a[0];
out[1] = a[1];
return out;
};
if (input.length > 0) output.push(copy([0, 0], input[0]));
for (let i = 0; i < input.length - 1; i++) {
const p0 = input[i];
const p1 = input[i + 1];
const p0x = p0[0];
const p0y = p0[1];
const p1x = p1[0];
const p1y = p1[1];
const Q = [0.75 * p0x + 0.25 * p1x, 0.75 * p0y + 0.25 * p1y];
const R = [0.25 * p0x + 0.75 * p1x, 0.25 * p0y + 0.75 * p1y];
output.push(Q);
output.push(R);
}
if (input.length > 1) output.push(copy([0, 0], input[input.length - 1]));
return output;
};
if (itr === 0) return points;
const smoothed = smoothFn(points);
return itr === 1 ? smoothed : chaikinSmooth(smoothed, itr - 1);
};
const drawPoints = (pointsArray, pColor = 'red', lColor = 'blue') => {
// Loop over the array and pass in the point and the current index
pointsArray.forEach((point, idx) => {
c.noStroke();
c.fill(pColor);
c.circle(point[0], point[1], 5);
// Draw a line between the current points, and the next one
// If it's the last point, don't do anything
const next = idx < pointsArray.length - 1 ? pointsArray[idx + 1] : null;
if (next) {
const px1 = point[0];
const py1 = point[1];
const px2 = next[0];
const py2 = next[1];
c.stroke(lColor);
c.line(px1, py1, px2, py2);
}
});
};
const draw = () => {
const cw = c.width; // Width of the canvas
const ch = c.height; // Height of the canvas
const m = 200; // Margin
const x1 = m;
const x2 = cw - m;
const y1 = ch / 2 - 50;
const y2 = ch / 2 + 50;
const points = [];
const pointsToInsert = randomWholeBetween(10, 50); // Will insert 1 more than this
const xIncrement = (x2 - x1) / pointsToInsert;
const yIncrement = (y2 - y1) / pointsToInsert;
let currentX = x1;
let currentY = y1;
const minOffset = 50;
const maxOffset = 100;
for (let i = 0; i <= pointsToInsert; i++) {
// Only move around the middle points
if (i > 0 && i < pointsToInsert) {
// Random radius between the min and max
const rRadius = randomNumberBetween(minOffset, maxOffset);
// Random radians between 0 and 2PI, the full circle
const rRadians = randomNumberBetween(0, Math.PI * 2);
const offsetX = currentX + rRadius * Math.cos(rRadians);
const offsetY = currentY + rRadius * Math.sin(rRadians);
points.push([offsetX, offsetY]);
} else {
points.push([currentX, currentY]);
}
currentX += xIncrement;
currentY += yIncrement;
}
const smoothPoints = chaikinSmooth(points, 3);
drawPoints(points, 'red', 'blue');
drawPoints(smoothPoints, 'green', 'green');
return false;
};
```