# Pin object to the end of a rope

I'm trying to make a cool simulation where a rope swings with an object attached to its end. I've set up the rope using Verlet integration and used Hooke's law to attach the object. But here's the catch: the object's movement is all over the place!

I thought about just sticking the object to the end of the rope by setting its position to the rope last point's position, but this method won't account for the forces acting on the object, such as gravity.

Here's the code I'm using:

extends Node3D

class VerletRope:

class Point:
var locked: bool = false
var pos: Vector3 = Vector3()
var last_pos: Vector3 = Vector3()

func _init(pos: Vector3, locked: bool = false):
self.pos = pos
self.last_pos = pos
self.locked = locked

class Stick:
var point_a: Point
var point_b: Point
var length: float

func _init(point_a: Point, point_b: Point, length: float):
self.point_a = point_a
self.point_b = point_b
self.length = length

var points: Array[Point] = []
var sticks: Array[Stick] = []
var gravity = 100.0
var num_iterations = 30

func _init(from: Vector3, to: Vector3, segment_distance: float, num_iterations: int = 30, gravity: float = 100.0) -> void:
self.gravity = gravity
self.num_iterations = num_iterations

var rope_length = from.distance_to(to)
var dir = from.direction_to(to)
var segment_count = int(rope_length / segment_distance)
var last_point = null

for i in range(segment_count + 1):
var pos = from + dir * (i * segment_distance)
var point = Point.new(pos)
self.points.append(point)

if last_point:
var length = last_point.pos.distance_to(point.pos)
var stick = Stick.new(last_point, point, length)
self.sticks.append(stick)

last_point = point

func simulate(delta: float) -> void:
for point in points:
if not point.locked:
var last_pos = point.pos
point.pos += point.pos - point.last_pos
point.pos += Vector3.DOWN * gravity * delta * delta
point.last_pos = point.pos

for i in range(num_iterations):
for stick in sticks:
var center = (stick.point_a.pos + stick.point_b.pos) / 2.0
var dir = (stick.point_a.pos - stick.point_b.pos).normalized()

if not stick.point_a.locked:
stick.point_a.pos = center + dir * stick.length / 2.0

if not stick.point_b.locked:
stick.point_b.pos = center - dir * stick.length / 2.0

@onready var verlet = VerletRope.new($Pole.position + Vector3(0, 2.0, 0), ball.position, 0.5) var gravity = 9.81 var mass = 1.0 var velocity = Vector3() var spheres = [] func _ready(): verlet.points[0].locked = true verlet.points[-1].locked = true for point in verlet.points: var sphere = CSGSphere3D.new() sphere.radius = 0.1 add_child(sphere) sphere.position = point.pos spheres.append(sphere) func _physics_process(delta): verlet.points[-1].pos = ball.position verlet.simulate(delta) for i in range(verlet.points.size()): spheres[i].position = verlet.points[i].pos var net_force = Vector3() var tension = calculate_tension(ball.position, verlet.points[-2].pos) net_force.y -= mass * gravity net_force += tension var acceleration = net_force/ mass velocity += acceleration * delta ball.position += velocity * delta func calculate_tension(start: Vector3, end: Vector3): var displacement = end - start var spring_constant = 100.0 var force = displacement * spring_constant return force  You can check out the source along with the project files here too. My goal is to get that smooth, natural movement between the rope and the object, just like in the pictures. Any tips or ideas would be appreciated, thanks in advance for the help! • It looks like the motion of the mass at the end of the chain is having a tough time propagating up the chain. You might want to try adjusting the way you iterate the "sticks". Do you notice any difference if you iterate from the bottom up, or ping-pong between an upward and downward sweep? You might also want to consider solving constraints with two neighbouring points at once, which might help the energy transfer along. Mar 18 at 11:37 • Hey, thanks for the suggestions. I tried them out but couldn't quite get it to work with Verlet. I ended up ditching it and dealing only with spring forces. I might try to fix it later, but for now, that's good enough Mar 18 at 18:29 ## 1 Answer So, after some trial and error was able to reproduce something not that terrible!!! Ended up ditching verlet and dealing only with spring instead. Here's the result: Here's the working source code if you need: https://github.com/SmiteIsTrashBro/RopePhysics/blob/master/spring_rope_test_3d.gd extends Node3D @onready var sphere_1 =$Sphere1

var spring_constant = 100.0
var gravity = 9.80
var damping_rate = 1.0
var velocity = Vector3()
var density = 1.0

class Point:
var pos: Vector3 = Vector3()
var force: Vector3 = Vector3()
var velocity: Vector3 = Vector3()
var rest_length: float = 0.0
var mass: float = 0.0

func _init(pos: Vector3):
self.pos = pos

var points = []
var spheres = []

var initial_pos = sphere_1.position
var final_pos = sphere_2.position
var segment_interval = 0.5
var distance = initial_pos.distance_to(final_pos)
var dir = initial_pos.direction_to(final_pos)
var segment_count = ceil(distance / segment_interval)
var corrected_interval = distance / segment_count
var segment_mass = density * distance / segment_count

for i in range(segment_count + 1):
var pos = initial_pos + dir * (corrected_interval * i)
var point = Point.new(pos)
point.rest_length = corrected_interval
point.mass = segment_mass
points.append(point)

var sphere = CSGSphere3D.new()
spheres.append(sphere)

func _physics_process(delta):
if Input.is_action_pressed("ui_right"):
points[0].pos.x += 5.0 * delta
if Input.is_action_pressed("ui_left"):
points[0].pos.x -= 5.0 * delta

for i in range(1, points.size() - 1):
var prev_point = points[i - 1]
var point = points[i]
var next_point = points[i + 1]

var f_spring0 = calculate_spring_force(point, prev_point)
var f_spring1 = calculate_spring_force(next_point, point)
point.force = f_spring0 - f_spring1

points[-1].force = calculate_spring_force(points[-1], points[-2])

for i in range(1, points.size()):
var point = points[i]
var f_gravity = Vector3(0, -point.mass * gravity, 0)
var f_damping = -point.velocity * damping_rate
var acceleration = (point.force + f_gravity + f_damping) / point.mass
point.velocity += acceleration * delta
point.pos += point.velocity * delta

for i in range(points.size()):
spheres[i].position = points[i].pos

sphere_1.position = points[0].pos
sphere_2.position = points[-1].pos

func calculate_spring_force(from: Point, to: Point):
var displacement = from.pos - to.pos
var x = displacement.length() - from.rest_length
var f_spring = -spring_constant * x * displacement.normalized()

return f_spring


Some resources I stumbled uppon during my research: