Verlet ragdoll

Hi all, I just wanted to show off my verlet structures a bit. I tried making a ragdoll and I think it works pretty nice!

https://m.youtube.com/watch?v=F5QcPrUzWLc

The poor guy was made using a verlet structure class I made a little while ago. All it does is create points and sticks. The points are moved using verlet integration and the sticks keep them apart.

https://m.youtube.com/watch?v=Dk3gkdCi-zU

B)

That’s awesome. Are you using box2D for that? If so, how are you constraining the box2D joints?

Very nice!

Thanks! :slight_smile:

I’m not using box2d, the limb constraints are just sticks with a low stiffness (thin red lines). So they have some freedom to move, but the further they are from their rest length the more they will try to push/pull back.

Basically it’s just points held together with springs, but the verlet integration keeps it from blowing up and also automagically handles stuff like inertia and center of mass (without any specific code). It isn’t any more difficult or heavy than euler either.

Euler = set velocity → position + velocity
Verlet = set position → position + (position-oldPosition)

So verlet integration only takes the change in position and uses that for the velocity. By moving the points, the velocity gets set automatically. It’s much more stable because the velocity will never exceed the previous velocity (which would add energy from nowhere).

Love it!

@Kirl Interesting demo. Can you share the code. If not, then how many lines of code is your verlet class. Just want to compare the size of verlet code to some box2d code.

The VerletStruct class is really just a manager/editor class to make and edit structures while running, it’s using two base classes: VerletPoint(x,y) and VerletStick(pnt1, pnt2, length, stiffness). The update functions (where the movement happens) are 4 and 5 lines respectively.

function VerletPoint:update()
    local op = self.pos
    self.vel = (self.pos-self.oldPos) * self.friction
    self.pos = self.pos + self.vel
    self.oldPos = op
end
function VerletStick:update()
    local delta = self.p2.pos - self.p1.pos
    local deltaLength = self.p1.pos:dist(self.p2.pos)
    local diff = (deltaLength-self.length)/deltaLength
    self.p1.pos = self.p1.pos + delta * self.stiffness * diff *self.p1.friction
    self.p2.pos = self.p2.pos - delta * self.stiffnes * diff *self.p2.friction
end

It’s perfect for soft bodies and many point collisions like fluids, but it’s not as good for rigid bodies (nowhere near box2d). Structures deform too easily and sort of squeeze out of the way at higher pressures. It’s a great effect if you’re looking for it, but definitely not well suited for rigid structures like box2d.

That said, it can do a decent job at single rigid shapes, multiple relaxation loops help but only up to a point. The above rope is very elastic for example, but increasing the relax loops makes it behave much more like a rigid rope (it depends on how many points/weights are in your structure).

@Kirl Thanks for the code above. I looked up Verlet ragdoll and I found some interesting reading. I converted what I could and made up the rest. Here’s what I got out of it. Slide your finger around the screen to move the ragdoll. Thought I’d share this in case anyone wants to see the full working code. I don’t know how this compares to yours.

displayMode(FULLSCREEN)

function setup()
    pt={}   -- point table
    con={}  -- constraint table
    createpoint(200,315,1,1)    -- x val, y val, x movement, y movement
    createpoint(200,300,0,0)
    createpoint(150,300,0,0)
    createpoint(250,300,0,0)
    createpoint(100,200,0,0)
    createpoint(200,200,0,0)
    createpoint(300,200,0,0)
    createconstraint(1,2)       -- constrain which points in pt table
    createconstraint(2,6)
    createconstraint(2,3)
    createconstraint(2,4)
    createconstraint(5,6)
    createconstraint(7,6)
end

function draw()
    background(0)
    updatepoint()
    updateconstraint()
    for z=1,#pt do   -- draw points
        fill(255)
        if z==1 then
            fill(255,0,0)
            ellipse(pt[z].nx,pt[z].ny,25)
        else
            ellipse(pt[z].nx,pt[z].ny,10)
        end
    end    
    stroke(255)
    strokeWidth(2)
    for z=1,#con do -- draw lines between constraint points
        line(pt[con[z].c1].nx,pt[con[z].c1].ny,pt[con[z].c2].nx,pt[con[z].c2].ny)
    end
end

function touched(t)
    if t.state==MOVING then
        pt[1].nx=pt[1].nx+t.deltaX/10
        pt[1].ny=pt[1].ny+t.deltaY/10
    end
end

function updatepoint()
    for z=1,#pt do
        local dx=pt[z].nx-pt[z].ox
        local dy=pt[z].ny-pt[z].oy
        pt[z].ox=pt[z].nx
        pt[z].oy=pt[z].ny
        pt[z].nx=pt[z].nx+dx
        pt[z].ny=pt[z].ny+dy
    end
end

function updateconstraint()
    for j=1,#con do
        t1=con[j].c1
        t2=con[j].c2
        local dist=math.sqrt((pt[t1].nx-pt[t2].nx)^2+(pt[t1].ny-pt[t2].ny)^2)
        local diff=dist-con[j].len                        
        local dx=pt[t1].nx-pt[t2].nx
        local dy=pt[t1].ny-pt[t2].ny
        if con[j].len>0 then
            diff=diff/con[j].len
        else
            diff=0
        end
        dx=dx*.5
        dy=dy*.5
        pt[t1].nx=pt[t1].nx-diff*dx
        pt[t1].ny=pt[t1].ny-diff*dy
        pt[t2].nx=pt[t2].nx+diff*dx
        pt[t2].ny=pt[t2].ny+diff*dy
    end
end

function createpoint(x,y,vx,vy)
    table.insert(pt,{nx=x,ny=y,ox=x-vx,oy=y-vy})
end

function createconstraint(p1,p2)
    length=math.sqrt((pt[p1].nx-pt[p2].nx)^2+(pt[p1].ny-pt[p2].ny)^2)
    table.insert(con,{c1=p1,c2=p2,len=length})
end

Here’s code to compare the motion of a verlet ragdoll and a box2d ragdoll. Red is verlet and green is box2d. Move your finger near the head of the ragdoll you want to move. Verlet code doesn’t handle collisions whereas the box2d does. The feet of the box2d ragdoll will bounce off its ragdoll head whereas the verlet will pass thru.

EDIT: Here’s a link I used for information.

http://www.blitzbasic.com/Community/posts.php?topic=84714
displayMode(FULLSCREEN)

function setup()
    fill(255)
    stroke(255)
    strokeWidth(2)
    
    -- box2d code
    physics.continuous=true
    physics.gravity(0,0)
    p1=physics.body(CIRCLE,1)
    p1.x=500
    p1.y=315    
    p2=physics.body(CIRCLE,1)
    p2.x=500
    p2.y=300
    p3=physics.body(CIRCLE,1)
    p3.x=450
    p3.y=300
    p3.restitution=.8 
    p4=physics.body(CIRCLE,1)
    p4.x=550
    p4.y=300   
    p4.restitution=.8 
    p5=physics.body(CIRCLE,1)
    p5.x=500
    p5.y=200
    p6=physics.body(CIRCLE,1)
    p6.x=400
    p6.y=200
    p6.restitution=.8 
    p7=physics.body(CIRCLE,1)
    p7.x=600
    p7.y=200
    p7.restitution=.8 
    jnt1=physics.joint(REVOLUTE,p1,p2,p1.position)
    jnt2=physics.joint(REVOLUTE,p2,p3,p2.position)
    jnt3=physics.joint(REVOLUTE,p2,p4,p2.position)
    jnt4=physics.joint(REVOLUTE,p2,p5,p2.position)
    jnt5=physics.joint(REVOLUTE,p5,p6,p5.position)
    jnt6=physics.joint(REVOLUTE,p5,p7,p5.position)
    
    -- verlet code
    pt={}   -- point table
    con={}  -- constraint table
    createpoint(200,315,0,0)    -- x val, y val, x movement, y movement
    createpoint(200,300,0,0)
    createpoint(150,300,0,0)
    createpoint(250,300,0,0)
    createpoint(100,200,0,0)
    createpoint(200,200,0,0)
    createpoint(300,200,0,0)
    createconstraint(1,2)       -- constrain which points in pt table
    createconstraint(2,6)
    createconstraint(2,3)
    createconstraint(2,4)
    createconstraint(5,6)
    createconstraint(7,6)
end

function draw()
    background(0)
    noStroke()
    fill(0,255,0)
    
    -- box2D code
    text("Box2D",500,HEIGHT-50)
    ellipse(p1.x,p1.y,25)
    ellipse(p2.x,p2.y,10)
    ellipse(p3.x,p3.y,10)
    ellipse(p4.x,p4.y,10)
    ellipse(p5.x,p5.y,10)
    ellipse(p6.x,p6.y,10)
    ellipse(p7.x,p7.y,10)
    stroke(0,255,0)
    strokeWidth(2)
    line(p1.x,p1.y,p2.x,p2.y)
    line(p2.x,p2.y,p3.x,p3.y)
    line(p2.x,p2.y,p4.x,p4.y)
    line(p2.x,p2.y,p5.x,p5.y)
    line(p5.x,p5.y,p6.x,p6.y)
    line(p5.x,p5.y,p7.x,p7.y)
    
    -- verlet code
    stroke(255,0,0)
    updatepoint()
    updateconstraint()
    fill(255,0,0)
    text("Verlet",200,HEIGHT-50)
    for z=1,#pt do   -- draw points
        if z==1 then
            ellipse(pt[z].nx,pt[z].ny,25)
        else
            ellipse(pt[z].nx,pt[z].ny,10)
        end
    end    
    for z=1,#con do -- draw lines between constraint points
        line(pt[con[z].c1].nx,pt[con[z].c1].ny,pt[con[z].c2].nx,pt[con[z].c2].ny)
    end
end

function touched(t)
    if t.state==MOVING then
        dist1=math.sqrt((pt[t1].nx-t.x)^2+(pt[t1].ny-t.y)^2)
        dist2=math.sqrt((p1.x-t.x)^2+(p1.y-t.y)^2)
        if dist1<dist2 then
            -- verlet code
            pt[1].nx=pt[1].nx+t.deltaX/10
            pt[1].ny=pt[1].ny+t.deltaY/10
        else
            -- box2d code
            p1.linearVelocity=vec2(t.deltaX*20,t.deltaY*20)
        end
    end
end

function updatepoint()  -- verlet code
    for z=1,#pt do
        local dx=pt[z].nx-pt[z].ox
        local dy=pt[z].ny-pt[z].oy
        pt[z].ox=pt[z].nx
        pt[z].oy=pt[z].ny
        pt[z].nx=pt[z].nx+dx
        pt[z].ny=pt[z].ny+dy
    end
end

function updateconstraint() -- verlet code
    for j=1,#con do
        t1=con[j].c1
        t2=con[j].c2
        local dist=math.sqrt((pt[t1].nx-pt[t2].nx)^2+(pt[t1].ny-pt[t2].ny)^2)
        local diff=dist-con[j].len                        
        local dx=pt[t1].nx-pt[t2].nx
        local dy=pt[t1].ny-pt[t2].ny
        if con[j].len>0 then
            diff=diff/con[j].len
        else
            diff=0
        end
        dx=dx*.5
        dy=dy*.5
        pt[t1].nx=pt[t1].nx-diff*dx
        pt[t1].ny=pt[t1].ny-diff*dy
        pt[t2].nx=pt[t2].nx+diff*dx
        pt[t2].ny=pt[t2].ny+diff*dy
    end
end

function createpoint(x,y,vx,vy) -- verlet code
    table.insert(pt,{nx=x,ny=y,ox=x-vx,oy=y-vy})
end

function createconstraint(p1,p2)    -- verlet code
    length=math.sqrt((pt[p1].nx-pt[p2].nx)^2+(pt[p1].ny-pt[p2].ny)^2)
    table.insert(con,{c1=p1,c2=p2,len=length})
end

Nice work Dave!

Just to be clear, Verlet integration refers to the method of using the velocity of the previous timestep, it has nothing to do with ragdolls or collision of itself. You can use it for anything, in this case a ragdoll.

Of course you still have to code collisions yourself, if you need those! :slight_smile:

@Kirl I never heard of verlet before your post. After reading about it, I saw that it didn’t do anything with collision and that it could be used for a lot of different things. I picked ragdoll because it didn’t take a lot of points to show what it did. I found this very interesting and it gave me something new to learn about and to try. I did the comparison just so people could see how similar they looked. Of course if you need collision, box2d is a lot easier.