Newton's Cradle

I created this to explore the limitations of the physics engine, but all in all it works pretty well. I needed to put some spacing in between the spheres to get the expected results.

-- Newton's Cradle
-- Herwig Van Marck
function setup()
    parameter("restitution",0.6,1,0.95)
    wall=physics.body(CHAIN,true,vec2(0,0),vec2(WIDTH,0),vec2(WIDTH,HEIGHT),vec2(0,HEIGHT))
    cradle=Cradle(6,restitution)
end

function collide(contact)
    if (contact.state==BEGAN) then
        --print(contact.normalImpulse)
        sound({StartFrequency = 0.5, AttackTime = 0,
            SustainTime = 0.133127, SustainPunch = 1, DecayTime = 0,
            MinimumFrequency = 0, Slide = 0.5, DeltaSlide = 0.5,
            VibratoDepth = 0.5, VibratoSpeed = 0.5, ChangeAmount = 0.5,
            ChangeSpeed = 0.5, SquareDuty = 0.5, DutySweep = 0.5,
            RepeatSpeed = 0.5, PhaserSweep = 0.5, LowPassFilterCutoff = 1, 
            LowPassFilterCutoffSweep = 0.5, LowPassFilterResonance = 0.5, 
            HighPassFilterCutoff = 0.5, HighPassFilterCutoffSweep = 0.5, 
            Waveform = 3, Volume = contact.normalImpulse/30.0})
    end
end

function drawObj(body)
    pushMatrix()
    pushStyle()
    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
        local range = #points - 1
        if (true) then -- todo add test for closed
            range = #points
        end
        for j = 1, range do
            a = points[j]
            b = points[j % #points +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)
        strokeWidth(2.5)
        ellipse(0,0,body.radius*2)
    end
    popStyle()
    popMatrix()
end

function touched(touch)
    cradle:touched(touch)
end

function draw()
    cradle:setRestitution(restitution)
    background(0, 0, 0, 255)
    fill(170, 59, 237, 255)
    font("HoeflerText-Italic")
    fontSize(48)
    textMode(CENTER)
    text("Newton's Cradle",WIDTH/2,HEIGHT-60)
    drawObj(wall)
    cradle:draw()
end

HangingBall = class()

function HangingBall:init(x,wdth,rest)
    local w=10
    local h=HEIGHT/4
    self.x = x
    self.objs={}
    self.joints={}
    local top = physics.body(CIRCLE,10)
    top.x = x
    top.y = HEIGHT/2
    top.interpolate=true
    top.type=STATIC
    table.insert(self.objs,top)
    local rope=physics.body(POLYGON,
     vec2(-w/2,w/2), vec2(-w/2,w/2-h), vec2(w/2,w/2-h), vec2(w/2,w/2))
    rope.x = x
    rope.y = HEIGHT/2
    rope.interpolate=true
    table.insert(self.objs,rope)
    local joint = physics.joint(REVOLUTE, top,rope, top.position)
    table.insert(self.joints,joint)
    self.ball = physics.body(CIRCLE,wdth)
    self.ball.x = x
    self.ball.y = HEIGHT/2-h-w
    self.ball.interpolate=true
    self.ball.restitution=rest
    self.ball.friction=0.1
    joint=physics.joint(WELD,rope,self.ball,self.ball.position)
    table.insert(self.objs,self.ball)
    table.insert(self.joints,joint)
end

function HangingBall:drawObj(body)
    pushMatrix()
    pushStyle()
    translate(body.x, body.y)
    rotate(body.angle)
    
    if body.type == STATIC then
        stroke(255,255,255,255)
        fill(255, 255, 255, 255)
    elseif body.type == DYNAMIC then
        stroke(150,255,150,255)
        fill(150,255,150,255)
    elseif body.type == KINEMATIC then
        stroke(150,150,255,255)
        fill(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
        local range = #points - 1
        if (true) then -- todo add test for closed
            range = #points
        end
        for j = 1, range do
            a = points[j]
            b = points[j % #points +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)
        strokeWidth(2.5)
        ellipse(0,0,body.radius*2)
    end
    popStyle()
    popMatrix()
end

function HangingBall:draw()
    for i,body in ipairs(self.objs) do
        self:drawObj(body)
    end
end

function HangingBall:setRestitution(v)
    if (self.ball.restitution~=v) then
        self.ball.restitution=v
        self.ball.active=false
        self.ball.active=true
    end
end

function HangingBall:touched(touch)
    local touchPoint=vec2(touch.x,touch.y)
    return self.ball:testPoint(touchPoint)
end

Cradle = class()

function Cradle:init(nr,rest)
    self.objs={}
    self.touchMap={}
    
    for x=1,nr do
        local hb=HangingBall((x-(nr+1)/2)*50+WIDTH/2,24,rest)
        table.insert(self.objs,hb)
    end
end

function Cradle:setRestitution(v)
    for i,obj in ipairs(self.objs) do
        obj:setRestitution(v)
    end
end

function Cradle:draw()
    local gain = 2.0
    local damp = 0.5
    pushStyle()
    strokeWidth(8)
    stroke(255, 255, 255, 83)
    fill(255, 255, 255, 81)
    for k,v in pairs(self.touchMap) do
        local worldAnchor = v.obj.ball:getWorldPoint(v.anchor)
        local touchPoint = v.tp
        local diff = touchPoint - worldAnchor
        local vel = v.obj.ball:getLinearVelocityFromWorldPoint(worldAnchor)
        v.obj.ball:applyForce( (1/1) * diff * gain - vel * damp, worldAnchor)
        
        line(v.tp.x, v.tp.y, worldAnchor.x, worldAnchor.y)
        ellipse(v.tp.x, v.tp.y,50)
    end
    popStyle()
    
    for i,obj in ipairs(self.objs) do
        obj:draw()
    end
end

function Cradle:touched(touch)
    local touchPoint=vec2(touch.x,touch.y)
    if touch.state == BEGAN then
        --print(touchPoint.x,touchPoint.y)
        for i,obj in ipairs(self.objs) do
            if obj:touched(touch) then
                self.touchMap[touch.id] = 
                    {tp = touchPoint, obj = obj, anchor = obj.ball: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

That’s impressive @Herwig. A really elegant example of the verisimilitude behind the physics model.

I’m keeping that one around to show people when trying to explain the possibilities opened up by the physics API. Thanks!

I still haven’t gotten the hang go joints yet. Good example.

This was great! Really awesome work!