Max Planck

Stib’s constant: a Planck Limit for After Effects.

I’ve built my own version of built-in After Effects expressions functions. Here’s why.

The loopOut, loopIn, loopOutDuration and loopInDuration methods in After Effects are very useful tools that even non-scripters should learn to use, like the better-known wiggle method. By simply selecting one from a drop-down you can easily add repetition and continuation to keyframed properties. If you don’t know about them, go have a look at Adobe’s reference.

For scripters, one problem with them is that they can’t be combined with other temporal methods such as valueAtTime. So you can have a property with the loopOut method or with valueAtTime, but there’s no way to get the valueAtTime of a property that has been looped, or to loop a property that uses valueAtTime in its expression.

So if you want to muck around with time, and you want to add looping to your property you have to build your own version of the looping functions.

So what does Max Planck have to do with anything?

Max Planck

So here’s the challenge I was facing. I wanted to add an offset, and a multiplier to the value of a property so that I could duplicate the layer and have the property change at a different speed, and starting at a different time, all controlled by scripts. That meant I had to use valueAtTime, but I wanted the property to continue after the last keyframe, as if I had used loopOut(continue). So I had to write my own implementation of loopOut().

LoopOut(continue), when applied to a property will continue to change the property at the velocity it has at the last keyframe. So if it is applied to a position property and the property is moving on the x axis 10 pixels per second and -50 pixels per second on the y axis, at n seconds after the last keyframe the value will have changed by [n * 1, n * -5]. Adding that difference to the value at the final keyframe gives us k + [n * 1, n * -5] where k is the value at the last keyframe.

That should be easy enough to implement right? Just find the velocityAtTime at the time of the last keyframe and use that to offset the value.

Unfortunately there’s a hitch. If you measure the velocityAtTime of the property right at the last keyframe’s time it will always be zero. Obviously AE decides that the motion stops at the time of the last keyframe.

So we need to measure the velocity just a tiny bit before the keyframe. How tiny? Well, what we want to do is find the smallest possible increment which AE will accept, a sort of Planck Time for After Effects if you will. Behold:

σ = 0.000019531250000095 s

I call it stib’s constant, or σ. Basically this is the smallest time increment that After Effects will consider as being before a keyframe. I found it by trial and error – I worked backwards and got to 0.0000195312500000949999999999 which doesn’t work, and 0.0000195312500000950000000000 which did, so that seems to be the limit.

But being accurate to 20 digits is probably overkill. At 10-20s you’re halfway to the actual Planck Time, so if you’re using After Effects to do something which relies on zeptosecond accuracy (yes, that’s a real word), you’re probably using the wrong tool for the job.

For for most purposes 0.00002 seconds will do, or let’s be honest 0.002 seconds (one 20th of a frame at 25fps). But you could just copy and paste the number above if you want to be suuuuuuuper accurate, ish.

I’m not sure if it depends on the frame rate of the comp. I can’t be arsed trying it on anything except 25fps, again, if you find anything let me know.

TL;DR: So If I want to add my hand-rolled loopOut(“continue”) expression to an expression that already uses valueAtTime(), here’s what I do:

lastkeyTime=key(numKeys).time;
valueAtTime(time) +  (time - lastkeyTime) * velocityAtTime(lastkeyTime - 0.00002);

Here it is used in an expression that will offset the action a random amount–up to half the duration of the comp, and scale the speed somewhere between three quarters and one and a half times as fast as the keyframed action. It works for properties with any number of dimensions.

seedRandom(index, timeless=true);
lastkeyTime=key(numKeys).time;
timeOffset = (time-random(thisComp.duration-lastkeyTime*2)) * random (0.75, 1.5);

if (timeOffset > lastkeyTime){
   valueAtTime(timeOffset) +  (timeOffset - lastkeyTime) * velocityAtTime(lastkeyTime - 0.00002);
} else {
   valueAtTime(timeOffset)
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.