• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

rope expression

Explorer ,
Jan 01, 2023 Jan 01, 2023

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

TOPICS
Error or problem , Expressions

Views

447

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Explorer , Jan 21, 2023 Jan 21, 2023

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

Votes

Translate

Translate
Community Expert ,
Jan 01, 2023 Jan 01, 2023

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 02, 2023 Jan 02, 2023

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!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Jan 02, 2023 Jan 02, 2023

Copy link to clipboard

Copied

Exactly. And you could add some processing to calculate the tangents as an auto-bezier to smooth the segments.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 03, 2023 Jan 03, 2023

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: 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jan 21, 2023 Jan 21, 2023

Copy link to clipboard

Copied

LATEST

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:

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines