FXHASH

clauswilke

Building an Alien Clock


An explanation of the ideas and mathematical concepts used to create the animations shown in the one-year fx(hash) anniversary piece The Passing of Time.

Screenshot of the alien clock we will be building in this article.

For the fx(hash) one-year anniversary, I released a collection of 365 animated pieces called The Passing of Time. Each piece shows a complex arrangement of lines that move around in circular motions, stretch and contract, and leave trails as the move and intersect with each other.

If you take the time to watch one of these animations carefully (I encourage you to do so in live mode in full screen, ideally on a large monitor), it can seem like a sort of alien clock that tells us the time in a language we don't quite understand.

Here, I'm going to explain the concepts that I used to make The Passing of Time. Before I do so, however, I would like to mention that I had previously used some of the same concepts in works that made the clock-like appearance even more explicit. I called these works "Strange Movements," as they looked very much like the movements of a watch. You can find examples here or here.

The entire idea of The Passing of Time is based on rotation, so let's start with a simple scene that we rotate. Here, for demonstration purposes, our scene consists of a square, a circle, and a triangle, all made up of individual dots.

Basic scene setup we will be working with: A square, a circle, a triangle, all made up of dots.

To rotate such a scene, we would normally take the x and y coordinates of each individual dot, multiply them with a rotation matrix, and then use the result as the new, rotated coordinates to draw the dot. But here, we'll go a different route. Instead, we will first convert the x and y coordinates into polar coordinates. Polar coordinates specify the location of a point with a radius (the distance from the origin) and an angle (how much the point has been rotated). We can then rotate the scene by adding some value to the angle and subsequently converting back into cartesian coordinates.

The two JS functions that convert to and from polar coordinates look like this:

Javascript
const toPolar = (x, y) => ({r: Math.sqrt(x * x + y * y), a: Math.atan2(y, x) / (2 * Math.PI)})
const toCartesian = (r, a) => ({x: r * Math.cos(2 * Math.PI * a), y: r * Math.sin(2 * Math.PI * a)})

Here, x and y are the cartesian coordinates, and r and a are the polar coordinates (radius and angle, respectively).

Note that I've made an unusual choice here for the angle a. Normally, the angle would be specified in radians and run from 0 to two pi. However, I normalize it by two pi, so that it runs from 0 to 1. In other words, in my polar coordinate system an angle of 1 represents one full rotation, 0.5 represents a half rotation, and so on. This is very convenient to reason about.

With these two functions defined, the function to rotate our scene looks as follows:

Javascript
function rotateScene (scene, time) {
  let out = []
  
  for (let i = 0; i < scene.length; i++) {
    let p = scene[i]
    let polar = toPolar(p.x, p.y)
    let a = polar.a + time
    let cart = toCartesian(polar.r, a)
    out.push(cart)
  }
  return out
}

The most important line is let a = polar.a + time. We simply add the time variable to the angle in polar coordinates and then transform back. Note that the scene variable contains an array of points with an x and a y component each.

Our function to animate and render the scene looks something like the following:

Javascript
function animate () {
  window.requestAnimationFrame(animate)
  
  // clear frame
  ctx.fillStyle = '#FFFDF8'
  ctx.fillRect(0, 0, 2 * scale, 2 * scale)
  
  // draw scene
  ctx.save()
  ctx.translate(scale, scale) // place origin in center
  drawPoints(rotateScene(scene, 0.002 * time))
  ctx.restore()

  time += 1
}

Here, ctx is the canvas drawing context and scale is a parameter that translates uniform coordinates into pixel coordinates. I like to work with coordinates running from -1 to 1, but the HTML canvas works with pixel coordinates, so we need to translate between the two.

You can see the result of this code here.

The entire scene is rotated.

Now let's make things a little more interesting. Instead of simply incrementing the angle with a constant value proportional to time, we can use a value that fluctuates. Here, specifically, I want to achieve an effect where the scene is initially at rest, then starts moving, and then comes to rest again, and does so smoothly over and over. This is where it's useful that I have defined my angles such that a value of 1 corresponds to one full rotation. Now, to create more interesting movement, I can apply any function that maps the values from 0 to 1 to some other values, in such a way that for an angle of 0 I get out 0 and for an angle of 1 I get out 1. This guarantees that at the end we have one full rotation once more. Additionally, I impose that the function has a horizontal tangent both at 0 and at 1, so the animation is smooth and starts from and ends in a full stop. But this requirement is not necessary. As long as the slopes match at the two end points the animation will look smooth.

The function I will be using is a fifth-order polynomial with an auxiliary parameter s. The parameter s determines whether the polynomial rises early or late.

f(t)=t5+5t4(1t)+10(s+2)t3(1t)2+10(s2)t2(1t)3f(t)= t^5 + 5t^4(1-t) + 10(s+2)t^3(1-t)^2 + 10(s-2)t^2(1-t)^3
Plot of fifth-order polynomial f(t) as a function of t, for five different values of s ranging from 0 to 1.

In my JS code, the function calculating this mapping looks like this:

Javascript
const interpolate = (a, s) => {
  let t = a % 1,  // normalize angle to fall between 0 and 1
      t2 = t*t, t4 = t2*t2, u = 1-t, u2 = u*u, // second and fourth-order terms
      // calculate coefficients, +-2 is a tuning constant, can be different
      A = s + 2, B = s - 2
      // return fifth-order polynomial
      return t*t4 + 5*t4*u + A*10*t*t2*u2 + B*10*t2*u2*u
}

And now, in rotateScene(), we replace the previous line let a = polar.a + time with:

Javascript
let a = polar.a + interpolate(time, 0.5)

Now the entire scene is spinning back and forth in a more interesting manner, as you can see here. However, it's still just an overall rotation. This is where the auxiliary parameter s enters the picture. We can supply a different value of s for each point, for example as follows:

Javascript
let a = polar.a + interpolate(time, (i + 0.5)/scene.length)

Here, i is an integer running from 0 the number of points, which is given by scene.length. You can see the result from this interpolation approach here. Now the shapes unfold and distort as they rotate.

When each point gets its own parameter s in the angle interpolation formula the shapes deform and unfold as they rotate.

One aspect I don't like about the preceding example is that all the three shapes are perfectly in sync. They start and stop moving at the same time and they always move at the same speed. There are different ways in which we can make this more interesting, but a simple one is to shift the phase of the angle interpolation individually for each object. I can do this by introducing two new indices, one which counts the object (0 for square, 1 for circle, 2 for triangle, etc.) and one which counts the points within each object (0, 1, 2, ...). I define the following two functions to calculate these indices:

Javascript
const groupIdx = (i) => (Math.floor(i / m)) // m is a global variable holding the number of points per group
const withinGroupIdx = (i) => (i % m)

Then, my angle interpolation line in rotateScene() becomes:

Javascript
let a = polar.a + 
        interpolate(time + groupIdx(i) / 3, (withinGroupIdx(i) + 0.5)/m) - 
        interpolate(groupIdx(i) / 3, (withinGroupIdx(i) + 0.5)/m)

This may look complicated but is rather straightforward. We simply add a different constant to the time value for each group, time + 0/3, time + 1/3, time + 2/3 , and let the s value run from 0 to 1 within each group. And then, to make sure that we get the original angle polar.a when time == 0 we have to subtract the entire interpolate() call one more time but with the time variable removed.

There's one additional concept I'd like to introduce at this time. The animation will look much more interesting if we can add some motion blur, where the dots leave trails and the trails get longer the faster the dots move. This sounds like a complicated effect to achieve, but it's actually super simple. Instead of completely clearing the canvas each time we draw a new frame, by drawing a rectangle with a solid color (see the code for the animate() function above), we simply draw a partially transparent rectangle. This requires literally adding two characters to the code, like so:

Javascript
// clear frame
ctx.fillStyle = '#FFFDF840' // fill color was changed from #FFFDF8 to #FFFDF840 to make it partially transparent
ctx.fillRect(0, 0, 2 * scale, 2 * scale)

This creates output as illustrated in the following image. You can see a live demo here.

Rotated and distorted shapes with different phases per shape and with motion blur.

Finally, as The Passing of Time uses lines instead of dots for visualization, we have an additional opportunity for complexity: We can rotate the beginning and the end points of each line separately, and at different speeds. Depending on the relative ratio of the two speeds, the animation will either loop very quickly or take a long time to return to its starting point. Here, for demonstration, the faster speed is exactly twice as fast as the slower speed, and so the animation loops once the faster moving points have fully cycled twice. But for different ratios it could take a long time until the animation has fully looped.

I am demonstrating this concept here with a very simple scene setup, three groups of horizontal lines. It produces output as illustrated in the following image, and you can see a live demo here.

Screenshot of the final alien clock.

This already feels quite similar to some of the iterations of The Passing of Time.

The actual implementation of The Passing of Time has a few additional complexities that I have not described in detail here. All of them were taken from my earlier artwork Before/After, also released on fx(hash). You can read about the inner workings of Before/After here. The additional complexities are as follows: First, instead of simple shapes such as squares, triangles, or circles, I'm using point arrangements generated by t-SNE. Second, I'm varying colors within groups, to generate interesting color gradients. Third, I'm sometimes drawing filled, partially transparent areas between adjacent lines.

I hope you now have a better understanding of how The Passing of Time works, and I encourage you to carefully inspect the live animation in some of the iterations. Try to see if you can identify some of the concepts discussed here. If you would like to use any of the ideas explained in this article in your own work, please go ahead. I have made the code for all five live demos freely available under CC0, so you can use it however you please. You can find it on GitHub here.



UserEditionsTimeActions
348
over 2 years ago
fxhash marketplace 3.0
348
clauswilke
10
tz1aT6o...RSfXw7a
1
becoinz
2
Poskok
1
Myrddin
1
andtpj
1
CRGO Capital
1

stay ahead with our newsletter

receive news on exclusive drops, releases, product updates, and more