Those of you that have been using After Effects long enough to remember Motion Math might remember a very cool script called spring.mm. This script attached a "spring" between two layers using an equation for spring motion. When expressions came along, most of the Motion Math scripts were easily converted to expressions, but spring.mm was mysteriously absent.
It turns out that for some tasks, Motion Math was more adept than expressions. This is because there was a crucial difference in the way that Motion Math operated. Motion Math had the advantage of doing all its calculations for all frames at once. This means that Motion Math had the ability to preserve information from one frame to the next, which is essential for a simulation like this. Expressions, as you've probably discovered by now, have no such way of passing information from one frame to the next.
So what we want to do here is come up with some way to replicate the functionality of the Motion Math spring script, given the limitations of expressions.
A seasoned expression writer will develop a number of techniques to circumvent the problems encountered because of the lack of persistent data in expressions. There is a last resort, brute force method that will work when no other options are available and that is to set the expression up so that on each frame it recreates everything that has happened on previous frames. That means, for example, on the 100th frame the expression has to run the calculation 100 times (once for each previous frame and once for the current frame). Clearly, this can really bog things down if the calculation is complex and the comp is lengthy.
For short sequences however, this technique can be just the ticket and is what we will use here. So the idea is fairly simple - we'll create a loop where the expression starts at frame 0 and updates the spring calculation once for each frame until it reaches the current frame. We'll assume that the layer establishing the motion is called "leader" and we'll apply our expression to the position property of the other layer.
restLength = 20;
damp = .95;
leader = thisComp.layer("leader");
fDur = thisComp.frameDuration;
currFrame = Math.round(time / fDur);
p2 = position.valueAtTime(0);
v2 = 0;
for (f = 0; f <= currFrame; f++){
t = f*fDur;
p1 = leader.transform.position.valueAtTime(t);
delta = p2 - p1;
nDelta = normalize(delta);
a = 2 * nDelta * (length(delta) - restLength) * fDur;
v2 = (v2 - a) * damp;
p2 += v2;
}
p2
There is a less extreme version of this frame-by-frame technique that can be used if what you're doing involves only the most recent occurance of some event occuring at a previous frame. An example of this would be an animation that is triggered any time an audio level exceeds a certain threshold. In that case, you would loop from the current frame backwards in time until you find the event.
Note that this expression will generate a divide-by-zero error if the two layers ever occupy the same position. You may get it when you first set up the comp. If you do, just move one of the layers a little and re-enable the expression.