Physics Ragdoll

I really enjoyed playing with @Luatee’s Ragdoll surfer, and was pretty impressed by the Ragdoll specifically. So, I wondered if I could accomplish something similar with the built-in physics.

After a bit of fiddling, I’ve got something that I think is fairly realistic.

Video:

http://www.youtube.com/watch?v=Rzmp75Blv-E

EDIT: I’m not sure why the colors are all wonky in the video, YouTube must have added some kind of filter when I uploaded it. Sorry about that.

EDIT 2: I think I’ve fixed that issue.

Code:


--# Main
-- Ragdoll Physics

function setup()
    DebugDraw = PhysicsDebugDraw()
    
    guy = Ragdoll(WIDTH / 2, HEIGHT / 2)
    
    floor = physics.body(EDGE, vec2(0,0), vec2(WIDTH,0))
end

function draw()
    background(40, 40, 50)

    DebugDraw:draw()
end

function touched(t)
    DebugDraw:touched(t)
end

--# Ragdoll
Ragdoll = class()


local bodyBox = function(width, height, x, y)
    local box = physics.body(POLYGON, vec2(-width / 2, -height / 2), vec2(width / 2, -height / 2), vec2(width / 2, height / 2), vec2(-width / 2, height / 2))
    box.restitution = 0.1
    box.friction = 0.4
    box.position = vec2(x, y)

    return box
end

local bodyJoint = function(lower, upper, b1, b2, x, y)
    local joint = physics.joint(REVOLUTE, b1, b2, vec2(x, y))
    joint.enableLimit = true
    joint.lowerLimit = lower
    joint.upperLimit = upper
    
    return joint
end

function Ragdoll:init(x, y)
    self.bones, self.joints = {}, {}
    
    -- Sizes
    local headSize = 50
    local torsoWidth, torsoHeight = 30, 20
    local upperArmWidth, upperArmHeight = 13, 36
    local lowerArmWidth, lowerArmHeight = 12, 34
    local upperLegWidth, upperLegHeight = 15, 44
    local lowerLegWidth, lowerLegHeight = 12, 40
    
    -- Bones
    -- Head
    local head = physics.body(CIRCLE, headSize / 2)
    head.restitution = 0.3
    head.friction = 0.4
    head.position = vec2(x, y)
    
    self.bones[1] = head
    
    -- Torso
    self.bones[2] = bodyBox(torsoWidth, torsoHeight, x, y - headSize / 2 - torsoHeight / 2)

    self.bones[3] = bodyBox(torsoWidth, torsoHeight, x, y - headSize / 2 - torsoHeight * 1.5)

    self.bones[4] = bodyBox(torsoWidth, torsoHeight, x, y - headSize / 2 - torsoHeight * 2.5)
    
    -- Arms
    self.bones[5] = bodyBox(upperArmWidth, upperArmHeight, x - torsoWidth / 2 - upperArmWidth / 2, y - headSize / 2 - upperArmHeight / 2)
    
    self.bones[6] = bodyBox(upperArmWidth, upperArmHeight, x + torsoWidth / 2 + upperArmWidth / 2, y - headSize / 2 - upperArmHeight / 2)
    
    self.bones[7] = bodyBox(lowerArmWidth, lowerArmHeight, x - torsoWidth / 2 - upperArmWidth / 2, y - headSize / 2 - upperArmHeight - lowerArmHeight / 2)
    
    self.bones[8] = bodyBox(lowerArmWidth, lowerArmHeight, x + torsoWidth / 2 + upperArmWidth / 2, y - headSize / 2 - upperArmHeight - lowerArmHeight / 2)
    
    -- Legs
    self.bones[9] = bodyBox(upperLegWidth, upperLegHeight, x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight / 2)
    
    self.bones[10] = bodyBox(upperLegWidth, upperLegHeight, x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight / 2)
    
    self.bones[11] = bodyBox(lowerLegWidth, lowerLegHeight, x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight - lowerLegHeight / 2)
    
    self.bones[12] = bodyBox(lowerLegWidth, lowerLegHeight, x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight - lowerLegHeight / 2)
    
    -- Joints
    
    -- Head to torso
    self.joints[1] = bodyJoint(-40, 40, self.bones[2], self.bones[1], x, y - headSize / 2)
    
    -- Upper arms to torso
    self.joints[2] = bodyJoint(-85, 130, self.bones[2], self.bones[5], x - torsoWidth / 2, y - headSize / 2)
    self.joints[3] = bodyJoint(-130, 85, self.bones[2], self.bones[6], x + torsoWidth / 2, y - headSize / 2)
    
    -- Upper arms to lower arms
    self.joints[4] = bodyJoint(-130, 10, self.bones[5], self.bones[7], x - torsoWidth / 2 - upperArmWidth / 2, y - headSize / 2 - upperArmHeight)
    self.joints[5] = bodyJoint(-10, 130, self.bones[6], self.bones[8], x + torsoWidth / 2 + upperArmWidth / 2, y - headSize / 2 - upperArmHeight)
    
    -- Torsos (Shoulders -> Stomach -> Hips)
    self.joints[6] = bodyJoint(-15, 15, self.bones[2], self.bones[3], x, y - headSize / 2 - torsoHeight)
    self.joints[7] = bodyJoint(-15, 15, self.bones[3], self.bones[4], x, y - headSize / 2 - torsoHeight * 2)
    
    -- Hips to upper legs
    self.joints[8] = bodyJoint(-25, 45, self.bones[4], self.bones[9], x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3)
    self.joints[9] = bodyJoint(-45, 25, self.bones[4], self.bones[10], x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3)
    
    -- Upper legs to lower legs
    self.joints[10] = bodyJoint(-25, 115, self.bones[9], self.bones[11], x - upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight)
    self.joints[11] = bodyJoint(-115, 25, self.bones[10], self.bones[12], x + upperLegWidth / 2, y - headSize / 2 - torsoHeight * 3 - upperLegHeight)
    
    
    
    for i, bone in ipairs(self.bones) do
        DebugDraw:addBody(bone)
    end
    
    for i, joint in ipairs(self.joints) do
        DebugDraw:addJoint(joint)
    end
end
--# PhysicsDebugDraw
PhysicsDebugDraw = class()

function PhysicsDebugDraw:init()
    self.bodies = {}
    self.joints = {}
    self.touchMap = {}
    self.contacts = {}
end

function PhysicsDebugDraw:addBody(body)
    table.insert(self.bodies,body)
end

function PhysicsDebugDraw:addJoint(joint)
    table.insert(self.joints,joint)
end

function PhysicsDebugDraw:clear()
    -- deactivate all bodies
    
    for i,body in ipairs(self.bodies) do
        body:destroy()
    end
  
    for i,joint in ipairs(self.joints) do
        joint:destroy()
    end      
    
    self.bodies = {}
    self.joints = {}
    self.contacts = {}
    self.touchMap = {}
end

function PhysicsDebugDraw:draw()
    
    pushStyle()
    smooth()
    strokeWidth(5)
    stroke(128,0,128)
    
    local gain = 2.0
    local damp = 0.5
    for k,v in pairs(self.touchMap) do
        local worldAnchor = v.body:getWorldPoint(v.anchor)
        local touchPoint = v.tp
        local diff = touchPoint - worldAnchor
        local vel = v.body:getLinearVelocityFromWorldPoint(worldAnchor)
        v.body:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor)
        
        line(touchPoint.x, touchPoint.y, worldAnchor.x, worldAnchor.y)
    end
    
    stroke(0,255,0,255)
    strokeWidth(5)
    for k,joint in pairs(self.joints) do
        local a = joint.anchorA
        local b = joint.anchorB
        line(a.x,a.y,b.x,b.y)
    end
    
    stroke(255,255,255,255)
    noFill()
    
    
    for i,body in ipairs(self.bodies) do
        pushMatrix()
        translate(body.x, body.y)
        rotate(body.angle)
    
        if body.type == STATIC then
            stroke(255,255,255,255)
        elseif body.type == DYNAMIC then
            stroke(150,255,150,255)
        elseif body.type == KINEMATIC then
            stroke(150,150,255,255)
        end
    
        if body.shapeType == POLYGON then
            strokeWidth(3.0)
            local points = body.points
            for j = 1,#points do
                a = points[j]
                b = points[(j % #points)+1]
                line(a.x, a.y, b.x, b.y)
            end
        elseif body.shapeType == CHAIN or body.shapeType == EDGE then
            strokeWidth(3.0)
            local points = body.points
            for j = 1,#points-1 do
                a = points[j]
                b = points[j+1]
                line(a.x, a.y, b.x, b.y)
            end      
        elseif body.shapeType == CIRCLE then
            strokeWidth(3.0)
            line(0,0,body.radius-3,0)            
            ellipse(0,0,body.radius*2)
        end
        
        popMatrix()
    end 
    
    stroke(255, 0, 0, 255)
    fill(255, 0, 0, 255)

    for k,v in pairs(self.contacts) do
        for m,n in ipairs(v.points) do
            ellipse(n.x, n.y, 10, 10)
        end
    end
    
    popStyle()
end

function PhysicsDebugDraw:touched(touch)
    local touchPoint = vec2(touch.x, touch.y)
    if touch.state == BEGAN then
        for i,body in ipairs(self.bodies) do
            if body.type == DYNAMIC and body:testPoint(touchPoint) then
                self.touchMap[touch.id] = {tp = touchPoint, body = body, anchor = body:getLocalPoint(touchPoint)} 
                return true
            end
        end
    elseif touch.state == MOVING and self.touchMap[touch.id] then
        self.touchMap[touch.id].tp = touchPoint
        return true
    elseif touch.state == ENDED and self.touchMap[touch.id] then
        self.touchMap[touch.id] = nil
        return true;
    end
    return false
end

function PhysicsDebugDraw:collide(contact)
    if contact.state == BEGAN then
        self.contacts[contact.id] = contact
        sound(SOUND_HIT, 2643)
    elseif contact.state == MOVING then
        self.contacts[contact.id] = contact
    elseif contact.state == ENDED then
        self.contacts[contact.id] = nil
    end
end

The code borrows the PhysicsDebugDraw class from the physics example because I didn’t want to waste time drawing and touching the bodies when I could just use this existing code.

I’ve updated the code to include a bridge, allow for any color on the physics bodies, and include texturing (which is disabled by default because it doesn’t look great with a mashup of Codea textures)

Here is the link to the latest: https://gist.github.com/JakAttak/0ac4bb579c17f1a2f93e

thumbs up?although i have a strange and wrong feeling?the guy might suffer from cruelties

=D> =D>

I love it

This is great @JakAttak, I’d love to try something like this: https://m.youtube.com/watch?v=rbUBAr7S_Wk