Menu

Tutorial 193: Sierpinski

Joe Clay | Sep 13, 2019

In building something for a client last week, I came up with an interesting movement using our generative paths from Tutorial 190: Cicada. At the same time I was doing more experiments with L-systems and so I coded a sort of Logo or Turtle graphics interpreter expression inside of After Effects. So this is the result of that.

The base premise is that we take the total number of points and fit them into a Trim Paths operation—or a pointOnPath() expression—by fitting the total number of points into 100. So we divide 100 by our total number of points, and set keys to move the end of our trim by that tiny increment. Then using loopOut('offset'), we have that tiny path move down the whole path. Setting our trim's start to be just a tiny bit away from the end effectively lets us use strokes that extend past the end like round or projecting caps to make circular or square dots.

There's a lot of additional things that can be done. After recording, I even built a system to put layers at specific positions matching the points of these generated paths so that anything could follow the paths in this mechanical fashion. By adding multiple points together, you could even make interesting animated shapes with these, which we'll probably do for Patron files this month. Also, that position setup found it's way into the project files if you want to buy them below.

Have fun experimenting with this! It's a rabbit hole.

Expression Code

Make sure to add all of the slider controls necessary as well as an angle control and rename them. We need Iterations, Distance, and the angle control is called Angle. The expression below should be applied to a path in a shape layer.

cmd would be the axiom or start. And the rules go in the map object. And the replace method within the first for loop. This first one makes the Dragon curve.

pts = [];
var x = y = 0;
var a = -90;
var cmd = 'FX';
var iterations = thisComp.layer("Controller").effect("Iterations")("Slider");
var d = thisComp.layer("Controller").effect("Distance")("Slider");
var angle = thisComp.layer("Controller").effect("Angle")("Angle");
var map = { X:"X+YF+", Y:"-FX-Y"};
for(var i = 0; i < iterations; i++) {
    cmd = cmd.replace(/X|Y/g, function(matched) {
        return map[matched];
    })
}
pts.push([x,y]);
for(var i = 0; i < cmd.length; i++) {
    switch(cmd.charAt(i)) {
        case 'F':
            x += Math.cos(degreesToRadians(a)) * d;
            y += Math.sin(degreesToRadians(a)) * d;
            pts.push([x,y]);
            break;
        case '+':
            a += angle;
            break;
        case '-':
            a -= angle;
            break;
    }
}
createPath(pts, [], [], false);

This one creates the Sierpinski curve.

pts = [];
var x = y = 0;
var a = -60;
var cmd = 'A';
var iterations = thisComp.layer("Controller").effect("Iterations")("Slider");
var d = thisComp.layer("Controller").effect("Distance")("Slider");
var angle = thisComp.layer("Controller").effect("Angle")("Angle");
var map = { A:"B-A-B", B:"A+B+A"};
for(var i = 0; i < iterations; i++) {
    cmd = cmd.replace(/A|B/gi, function(matched) {
        return map[matched];
    })
}
pts.push([x,y]);
for(var i = 0; i < cmd.length; i++) {
    switch(cmd.charAt(i)) {
        case 'B':
        case 'A':
            x += Math.cos(degreesToRadians(a)) * d;
            y += Math.sin(degreesToRadians(a)) * d;
            pts.push([x,y]);
            break;
        case '+':
            a += angle;
            break;
        case '-':
            a -= angle;
            break;
    }
}
createPath(pts, [], [], false);

And this one makes my...uh...Cauliflower curve. Good luck with it.

pts = [];
var x = y = bufferX = bufferY = 0;
var a = bufferA = -60;
var cmd = 'X';
var iterations = thisComp.layer("Controller").effect("Iterations")("Slider");
var d = thisComp.layer("Controller").effect("Distance")("Slider");
var angle = thisComp.layer("Controller").effect("Angle")("Angle");
var map = { X:"F+[[X]-X]-F[-FX]+X", F:"FF"};
for(var i = 0; i < iterations; i++) {
    cmd = cmd.replace(/X|F/gi, function(matched) {
        return map[matched];
    })
}
pts.push([x,y]);
for(var i = 0; i < cmd.length; i++) {
    switch(cmd.charAt(i)) {
        case 'F':
            x += Math.cos(degreesToRadians(a)) * d;
            y += Math.sin(degreesToRadians(a)) * d;
            pts.push([x,y]);
            break;
        case '+':
            a += angle;
            break;
        case '-':
            a -= angle;
            break;
        case '[':
            bufferX = x;
            bufferY = y;
            bufferA = a;
            break;
        case ']':
            x = bufferX;
            y = bufferY;
            a = bufferA;
            break;
    }
}
createPath(pts, [], [], false);

Grab the Project Files

Get the project file through our Gumroad Store. This project file contains everything shown in the tutorial. It also includes other elements used in the thumbnail, as well as an additional rig for the GIF that was created after the tutorial was rendered. The After Effects project was built in AE CC 2019 and 2019 is required. You might get a notice that you need a plugin or a font, but they aren't required to use this effect. They're mainly used for the thumbnail.

If you're buying project files, consider becoming a Patron. At the $5/mo. tier, you get access to project files as they come out and some tutorials also come with additional BTS content showing more of the builds.

Get the project on Gumroad

Become a Patron

If you'd like to help support Workbench, check out our Patreon page. Thank you for even considering clicking this link to support what we're doing. We appreciate it. Patrons get all sorts of benefits, from additional files to early product releases depending upon the tier.

Check out our Patreon Today