blog.hirnschall.net
home

Contents

Subscribe for New Projects

Motivation

Looping animations are everywhere: generative art, shaders, UI animations, game assets, and GIFs.
But creating perfectly looping procedural animations is surprisingly difficult.

A common approach is to animate some parameter over time:

This usually works, until the animation restarts. Then you get a visible jump.

The beginning and end don't match, because procedural randomness is typically not periodic.

The idea to fix this is simple, we need to create periodic randomness. But how do we create randomness that loops perfectly?

Perlin Noise Example

To get a feel for how we can implement periodic noise and how it works we will go through an example using perlin noise in p5js. We will generate a simple 2D terrain and create a perfectly looping gif.

The technique shown is applicable to other procedural animation/generation as well (not just perlin noise gifs).

If you are not familiar with perlin noise, you can take a look at this article explaining what perlin-noise is and how we can use it in procedural generation.

Naive Approach

Lets start with this bad looping gif of a 2d "map" scrolling from right to left:

Figure 1.1: Naive Approach to Looping Animations

You can clearly see the cut where the gif starts from the beginning which is something we don't want.

You can see side by side which section of perlin noise we are using to create the current animation frame and the corresponding frame in fig. 1.2.

Figure 1.2: How Bad Looping Animations Work

Closed Paths

The reason why the animation in fig. 1.1 doesn't loop smoothly is because it follows a straight line path through the noise space. Thus the beginning and end of the path are different.

To alleviate this problem we can move the section of noise we work with in a circle instead of a straight line. By moving on a closed path the noise inherently becomes periodic and we won't have any cuts when the animation restarts.

You can see the result below:

Figure 1.3: Looping Animation on a Closed Path

Although fig. 1.3 shows that there is no longer an obvious cut when the video restarts, we see another obvious problem. The animation itself has changed. It is no longer scrolling but rather orbiting. We are now scrolling sideways.

Higher-Dimensional Noise

To overcome this problem we will take the idea of moving in a circle to three dimensions or in general, if we are using \(n\)-dimensional perlin noise in the original animation we'll now use \(n+1\) dimensions.

So what we'll do is map our flat canvas to the outside of a cylinder in 3d space. By doing so we know there won't be any cuts as we are still going in a circle but the animation will stay the same!

You can think of this as if we were to wrap the canvas in fig. 1.1 into a hollow cylinder.

We can do this by mapping the each point \((x,y)\) to \((x',y',z')\):

$$x'=\sin\left(\frac{x\cdot 2\pi}{U}\right)\cdot R$$ $$y'=y$$ $$z'=\begin{cases} \sqrt{R^2-x'^2} & \text{if $\frac{6\pi}{4} < (\frac{x\cdot 2\pi}{U}$ mod $2\pi) <\frac{2\pi}{4}$} \\ -\sqrt{R^2-x'^2} & \text{otherwise} \end{cases} $$

Where \(U\) is the circumference and \(R\) is the radius of the cylinder. By changing the size of the cylinder we can adjust the length of the final animation.

Note that in this example, to stick with the naming of the 2D case, \(y\) represents the height of the cylinder. This might be counter intuitive as we expect \(z\) to represent the height.

As (in p5js at least) \(noise(x)=noise(-x)\) we will offset the center of our cylinder from \(0\). This can be done by adding \(R\) to each coordinate. So, we'll use \(noise(R+x',R+y',R+z')\) instead of \(noise(x,y)\).

You can see the section of noise we are using for the final animation in fig. 1.4 below. However it is hard to visualize 3d noise correctly..

Figure 1.4: Section of 3D Perlin Noise used to Create Periodic Animations (perfectly looping)

And the final animation without any jumps or cuts:

Figure 1.5: Perfectly looping gif

Conclusion

By sampling higher-dimensional noise along a closed path we can create perfectly looping procedural animations. This technique can be applied to any procedural animation that relies on randomness and is not inherently periodic.

All in all I am quite happy with the results. I think the idea of using an extra dimension is quite neat.

This post is part of multiple articles on Creative Programming (with p5.js).

Below are two related posts you might like:

Maze Generator in JavaScript

Maze Generator in JavaScript

Build a working maze generator using a random walker algorithm with a clean OOP structure in p5js.

Symbolic Engineering Solver (FSAE, Open Source)

Symbolic Engineering Solver (FSAE, Open Source)

Define equation templates, specify your knowns, let a symbolic solver derive the unknowns. Applicable to any physics system.

Get Notified of New Articles

Subscribe to get notified about new projects. Our Privacy Policy applies.
Sebastian Hirnschall
Article by: Sebastian Hirnschall
Updated: 04.04.2026

License

This project (with exceptions) is published under the CC Attribution-ShareAlike 4.0 International License.