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