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:
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: