Tutorial 171: Glitch Paths

Joe Clay | Apr 12, 2019

In this week's After Effects tutorial, we're actually back in After Effects. I was talking to Mikey Borup about messing with paths using expressions, and while he was working on something, I had an idea for something else. So we're going to explore glitching paths. The basic idea is to take paths and screw them up with code.

Mikey had done something cool with messing with path resolution, using pointOnPath(). So I built glitchy text by rebuilding paths with different points. Then I started to move those points around, and ultimately I started adding in my own blocky glitches.

Since we're just using paths, we can actually use any shape, so this isn't just limited to text. So experiment with different things. You can even add in path operations like the ones I mentioned from the Mo Better Blobs tutorial—and make sure to read the text for an update.

Now, here's some code for you to examine.

Expression Code

Note that all of these require slider controls. You can see which ones you need to make assigned to variables in the following expressions.

All of these are set to glitch randomly, so sometimes the original paths are shown. In the first one, we build an array of points at a specified resolution, and then we randomly select points from that array and move them before passing them back into createPath().

var origPath = thisProperty;
var freq = effect("Glitch Frequency")("Slider")/100;

if(random() < freq){
    var res = effect("Resolution")("Slider");
    var amp = effect("Amplitude")("Slider");
    var amt = effect("Glitch Amount")("Slider");
    var seed = effect("Seed")("Slider");
    var pts = new Array;
    var skip = 1/res;
    seedRandom(seed);

    for(var i = 0; i < res; i++){
        pts.push(origPath.pointOnPath(i*skip));

    }

    for(var i = 0; i < amt; i++){
        var j = Math.floor(random(res));
        var offset = [random(-amp,amp),random(-amp,amp)];
        pts[j] += offset;
        if(j < res - 1) {
            j++;
            pts[j] += offset;
        }
    }
    createPath(pts);
} else {
    origPath;
}

In this version, we've changed out glitches to happen randomly, and we're also using the timeless argument to seedRandom() so that our random numbers will only change every 2-3 frames. This allows out glitches to hold for a frame or two longer so it's less crazy but more distinctive—like animating on twos. We've also change our random movements to push out in x only, effectively pushing out boxes from our paths. Even though I pressed on, I think I actually prefer this one.

var origPath = thisProperty;
var freq = effect("Glitch Frequency")("Slider")/100;
var seed = effect("Seed")("Slider");
seed = seed + Math.floor(time*15);
seedRandom(seed, true);

if(random() < freq){
    var res = effect("Resolution")("Slider");
    var amp = effect("Amplitude")("Slider");
    var amt = effect("Glitch Amount")("Slider");
    var pts = new Array;
    var skip = 1/res;

    for(var i = 0; i < res; i++){
        pts.push(origPath.pointOnPath(i*skip));=
    }

    for(var i = 0; i < amt; i++){
        var j = Math.floor(random(1,res-2));
        var x = random(-amp,amp);
        pts[j] = [pts[j][0] + x, pts[j-1][1]];
        pts[j+1] = [pts[j+1][0] + x, pts[j+2][1]];
    }
    createPath(pts);
} else {
    origPath;
}

In this one, we've added in a function called buildBlock() so that we can bump out a block whenever we want. So we grab all of our points, and when we're done with that, we add in boxes up to the number specified in the amount slider. Those are concatenated to the end of the original points. So they're drawn in order after the original points, so that they'll intersect the existing geometry.

function buildBlock(a, b) {
    var x = random(-amp, amp);
    var block = new Array;
    block.push(a);
    block.push([a[0] + x, a[1]]);
    block.push([b[0] + x, b[1]]);
    block.push(b);
    return block;
}

var origPath = thisProperty;
var freq = effect("Glitch Frequency")("Slider")/100;
var seed = effect("Seed")("Slider");
seedRandom(seed);

if(random() < freq){
    var res = effect("Resolution")("Slider");
    var amp = effect("Amplitude")("Slider");
    var amt = effect("Glitch Amount")("Slider");
    var pts = origPath.points();
    var inTangents = origPath.inTangents();
    var outTangents = origPath.outTangents();
    var skip = 1/res;

    for(var i = 0; i < amt; i++) {
        var pt = random();
        var a = origPath.pointOnPath(pt - .01);
        var b = origPath.pointOnPath(pt + .01);
        pts = pts.concat(buildBlock(a, b));
        for(var j = 0; j < 4; j++) {
            inTangents.push([0,0]);
            outTangents.push([0,0]);
        }
    }
    createPath(pts, inTangents, outTangents);
} else {
    origPath;
}

This is the final expression. To save time and also make cleaner paths, we test to see if we should add a glitch and then we add it instead of the point we were going to add so that it occurs where it should along the path. This makes it less crazy. Earlier versions didn't have a the Glitch Spread slider and the spread variable. This allows you to force the glitches to occur less frequently, so that instead of showing up all at the top of the paths, they spread out. This is because if they will glitch points 80% of the time, most of the glitches will be used earlier in the process of building the paths leaving none for the rest. So adding in that slider allows you to fine tune it.

function buildBlock(a, b, glitches) {
    var x = random(-amp, amp);
    var block = new Array;
    block.push(a);
    block.push([a[0] + x, a[1]]);
    block.push([b[0] + x, b[1]]);
    block.push(b);
    return block;
}

var origPath = thisProperty;
var freq = effect("Glitch Frequency")("Slider")/100;
var seed = effect("Seed")("Slider");

seed += Math.floor(time*15);
seedRandom(seed, true);

if(random() < freq){
    var res = effect("Resolution")("Slider");
    var amp = effect("Amplitude")("Slider");
    var amt = effect("Glitch Amount")("Slider");
    var size = effect("Glitch Size")("Slider") * .01;
    var pts = new Array;
    var inTangents = new Array;
    var outTangents = new Array;
    var skip = 1/res;
    var glitches = 0;

    for(var i = 0; i < res; i++) {
        if(random() > .5 && glitches < amt) {
            var a = origPath.pointOnPath(i * skip - size);
            var b = origPath.pointOnPath(i * skip + size);
            pts = pts.concat(buildBlock(a, b));
            for(var j = 0; j < 4; j++) {
                inTangents.push([0,0]);
                outTangents.push([0,0]);
            }
            glitches ++;
        } else {
            pts.push(origPath.pointOnPath(i * skip));
            tangent = origPath.tangentOnPath(i * skip)*10;
            inTangents.push(mul(tangent, -1));
            outTangents.push(tangent);
        }
    }
    createPath(pts, inTangents, outTangents);
} else {
    origPath;
}

And that's it. I hope this helps you, or at least gets you thinking of the power that we finally have to control the destiny of our paths.

Grab the Project Files

The best way to get our project files is to become a patron on Patreon. For $5 a month, you get access to all of the tutorial project files we've made available as well as other monthly projects, rigs, R&D, elements, early product previews, and BTS content not available anywhere else! You can also purchase just this project file on our Gumroad Store if you would rather do that.

Get access to all of our project files on Patreon or Get this single project file on Gumroad