Copy link to clipboard
Copied
Hello guys. I'm currently working on the rope expression just to push my limits farther and see what is possible to do in the After Effects.
I did this expression - it uses Verlet Algorithm to save it's velocity, add other forces above that, and then rope constrain adjust position between anchor points. It partly works, but for some reason spring effect from the Verlet Algorithm doesn't work as I wanted - it appears from the first position, but not from the next position changes.
I guess the problem is that variable "pOld" doesn't save value of an object's position a frame ago. What may be the solution for this?
p = pOld = transform.position;
p1 = thisComp.layer(index - 1).transform.position; // anchor 1
p2 = thisComp.layer(index + 1).transform.position; // anchor 2
gravity = thisComp.layer("controller").effect("gravity")("Slider");
l = thisComp.layer("controller").effect("length")("Slider");
friction = thisComp.layer("controller").effect("friction")("Slider");
fDur = thisComp.frameDuration;
currFrame = Math.round((time - inPoint) / fDur);
for (i = 0; i <= currFrame; i++){
// verlet algorithm:
v = (p - pOld) * friction;
pOld = p
p += v;
p[1] += gravity;
// rope constraints 1:
delta = p1 - p;
nDelta = normalize(delta);
v1 = nDelta * (length(delta) - l) / 2;
// rope constraints 2:
delta = p2 - p;
nDelta = normalize(delta);
v2 = nDelta * (length(delta) - l) / 2;
p += v1;
p += v2;
}
p
There is also test project with this expression
Well, rope expression is pretty much done. It's competely working as intented. You can adjust amount of nodes to make rope more or less wiggly. I made 7 nodes, you can add or remove them adjusting amount of nodes and sticks. The downside is that expression becomes super laggy after 5-10 seconds, but for my needs it's super great. If anyone need it or want to try - here it is:
anchorStart = thisComp.layer("anchorStart"); // INSERT FIRST ANCHOR HERE
anchorEnd = thisComp.layer("anchorEnd"); // INSER
...
Copy link to clipboard
Copied
To deal with the lack of persistent data in expressions, it seems like you would need an inner loop and an outer loop. The inner loop would iterate to "solve" the rope's shape based on current conditions and the outer loop would loop through each previous fame so that each frame could set up the data for the next frame's solution. It seems like it could turn into a huge burden on the processor if your simulation was of considerable duration and you had a lot of nodes defining the rope.
However, it is an interesting idea to maybe use with the createPath() function to create a bezier-based rope with a handful of points.
Copy link to clipboard
Copied
Wow, this is really good idea! So I can use shape layer and generate all poinst in the one expression, and then create path out of it. Thanks Dan!
Copy link to clipboard
Copied
Exactly. And you could add some processing to calculate the tangents as an auto-bezier to smooth the segments.
Copy link to clipboard
Copied
Well, I think I did rope expression. It works better than before, your idea to put everything in the shape layer is amazing! But again, same problem with keyframes - expression calculates only the current position for the whole expression.
It's already big favor, but can I ask you what may be solution for this? Because I didn't get an idea how to implement inner and outer loops as you said earlier. It's like put all calculations for current positions of a nodes in the frame loop?
Because if it's the only way than it's gonna put on the knees my computer for sure. Maybe the other solution is to make some sort of script that is gonna calculate it? What do you think?
nodes = [];
sticks = [];
anchorStart = thisComp.layer("anchorStart").transform.position;
anchorEnd = thisComp.layer("anchorEnd").transform.position;
gravity = thisComp.layer("controller").effect("gravity")("Slider");
friction = thisComp.layer("controller").effect("friction")("Slider");
l = thisComp.layer("controller").effect("length")("Slider");
fDur = thisComp.frameDuration;
currFrame = Math.round((time - inPoint) / fDur);
// nodes:
nodes.push({ // first anchor
p: anchorStart,
pold: anchorStart,
pinned: true
});
nodes.push({
p: [0,0],
pold: [0,0]
});
nodes.push({
p: [50,50],
pold: [50,50]
});
nodes.push({
p: [60,60],
pold: [60,60]
});
nodes.push({ // second anchor
p: anchorEnd,
pold: anchorEnd,
pinned: true
});
// sticks - group of nodes to calculate rope constraints easier:
sticks.push({
p0: nodes[0],
p1: nodes[1]
});
sticks.push({
p0: nodes[1],
p1: nodes[2]
});
sticks.push({
p0: nodes[2],
p1: nodes[3]
});
sticks.push({
p0: nodes[3],
p1: nodes[4]
});
for(f = 0; f <= currFrame; f++){
// verlet integration:
for(i = 0; i < (nodes.length); i++){
var s = nodes[i];
// limitation to move not pinned nodes only:
if(!s.pinned){
v = (s.p - s.pold) * friction;
s.pold = s.p;
s.p += v;
s.p[1] += gravity;
}
}
// constraints:
for(i = 0; i < (sticks.length); i++){
var s = sticks[i];
delta = s.p0.p - s.p1.p;
nDelta = normalize(delta);
v = nDelta * (length(delta) - l) / 2;
// limitation for the first node:
if(!s.p0.pinned){
s.p0.p -= v;
}
// limitation for the second node:
if(!s.p1.pinned){
s.p1.p += v;
}
}
}
createPath(points = [nodes[0].p,nodes[1].p,nodes[2].p,nodes[3].p,nodes[4].p], inTangents = [], outTangents = [], isClosed = false)
There is also test ae project if you might be interested:
Copy link to clipboard
Copied
Well, rope expression is pretty much done. It's competely working as intented. You can adjust amount of nodes to make rope more or less wiggly. I made 7 nodes, you can add or remove them adjusting amount of nodes and sticks. The downside is that expression becomes super laggy after 5-10 seconds, but for my needs it's super great. If anyone need it or want to try - here it is:
anchorStart = thisComp.layer("anchorStart"); // INSERT FIRST ANCHOR HERE
anchorEnd = thisComp.layer("anchorEnd"); // INSERT SECOND ANCHOR HERE
gravity = thisComp.layer("controller").effect("gravity")("Slider");
friction = thisComp.layer("controller").effect("friction")("Slider");
l = thisComp.layer("controller").effect("length")("Slider");
fDur = thisComp.frameDuration;
currFrame = Math.round((time - inPoint) / fDur);
// nodes:
nodes = [];
nodesStartPos = anchorStart.transform.position.valueAtTime(0);
nodes.push({ // first anchor
p: 0,
pinned: true
});
nodes.push({
p: nodesStartPos,
pold: nodesStartPos
});
nodes.push({
p: nodesStartPos,
pold: nodesStartPos
});
nodes.push({
p: nodesStartPos,
pold: nodesStartPos
});
nodes.push({
p: nodesStartPos,
pold: nodesStartPos
});
nodes.push({
p: nodesStartPos,
pold: nodesStartPos
});
nodes.push({ // second anchor
p: 0,
pinned: true
});
// sticks - group of nodes to calculate rope constraints:
sticks = [];
sticks.push({
n0: nodes[0],
n1: nodes[1]
});
sticks.push({
n0: nodes[1],
n1: nodes[2]
});
sticks.push({
n0: nodes[2],
n1: nodes[3]
});
sticks.push({
n0: nodes[3],
n1: nodes[4]
});
sticks.push({
n0: nodes[4],
n1: nodes[5]
});
sticks.push({
n0: nodes[5],
n1: nodes[6]
});
for(f = 0; f <= currFrame; f++){
// anchor pos tracker:
currFrameLoop = (f * fDur).toFixed(2)
nodes[0].p = anchorStart.toWorld(anchorEnd.anchorPoint,currFrameLoop);
nodes[nodes.length - 1].p = anchorEnd.toWorld(anchorEnd.anchorPoint,currFrameLoop);;
// verlet integration:
for(i = 0; i < (nodes.length); i++){
var n = nodes[i];
// pin limitation:
if(!n.pinned){
v = (n.p - n.pold) * friction;
n.pold = n.p;
n.p += v;
n.p[1] += gravity;
}
}
// constraints:
for(i = 0; i < (sticks.length); i++){
var s = sticks[i];
delta = s.n0.p - s.n1.p;
nDelta = normalize(delta);
v = nDelta * (length(delta) - l) / 2;
// pin limitation for the first node:
if(!s.n0.pinned){
s.n0.p -= v;
}
// pin limitation for the second node:
if(!s.n1.pinned){
s.n1.p += v;
}
}
}
createPath(points = [nodes[0].p,nodes[1].p,nodes[2].p,nodes[3].p,nodes[4].p,nodes[5].p,nodes[6].p], inTangents = [], outTangents = [], false)
And Ae project if you want to try it all setted up: