物理エンジンを使う 9, ボディー同士をバネで拘束する, DampedSpring()

-- 物理エンジンを使う 9, ボディー同士をバネで拘束する, DampedSpring()
-- https://www.omnimaga.org/lua-language/chipmunk-physics/ を参考に一部クラス化した。
require "physics"
function pV(x, y) return physics.Vect(x, y) end
dt = 1/30
ZERO = pV(0, 0)
LARGE = physics.misc.INFINITY()
W, H = platform.window:width(), platform.window:height() 
gravity = 0
color = {0xFF00FF, 0x9900FF, 0x3399FF, 0x33CCFF, 0x00FFFF, 0x00FF33, 0xCCFF00, 0xFFFF00, 0xFFCC00, 0xFF9900, 0xFF3300, 0xFF00CC}

-----------------
-- pBall class --
-----------------
pBall = class()
function pBall:init(mass, radius, elasticity, friction, color, group)
   self.color = color or 0
   local inertia = physics.misc.momentForCircle(mass, 0, radius, ZERO)
   self.body = physics.Body(mass, inertia)
   self.shape = physics.CircleShape(self.body, radius, ZERO)
      :setRestitution(elasticity or 0.8)
      :setFriction(friction or 0.8)
      if group then self.shape:setGroup(group) end
end
function pBall:paint(gc)
   local cx, cy = self.body:pos():x(), self.body:pos():y()
   local radius = self.shape:radius()
   local diameter = radius + radius
   local angle = self.body:angle()
   local x0, y0 = cx - radius, cy - radius  
   gc:setColorRGB(self.color)
   gc:fillArc(x0, y0, diameter, diameter, 0, 360)
   gc:setColorRGB(0)
   gc:drawArc(x0, y0, diameter, diameter, 0, 360)
   gc:drawLine(cx, cy, radius * math.cos(angle) + cx, radius * math.sin(angle) + cy)
end

------------------
-- pSpace class --
------------------
pSpace = class()
function pSpace:init(gravity)
   self.space = physics.Space()
      :setGravity(pV(0, gravity))
   self.objects = {}
end
function pSpace:step(dt)
   self.space:step(dt)
end
function pSpace:addObj(obj, velx, vely, cx, cy)
   obj.body:setVel(pV(velx, vely))
           :setPos(pV(cx, cy))
   self.space:addBody(obj.body)
             :addShape(obj.shape)
   table.insert(self.objects, obj)
end
function pSpace:addStatic(obj, cx, cy)
   obj.body:setPos(pV(cx, cy))
   self.space:addStaticShape(obj.shape)
   table.insert(self.objects, obj)
end
function pSpace:paint(gc)
   for _, v in ipairs(self.objects) do
      v:paint(gc)
   end
   ---[[確認用に拘束線を点線で描く
   gc:setPen("thin", "dotted")
   gc:setColorRGB(0x8E8E38)
   local sute = {}
   for i = 1, #balls do
      table.insert(sute, balls[i].body:pos():x()); table.insert(sute, balls[i].body:pos():y())
   end
   table.insert(sute, balls[1].body:pos():x()); table.insert(sute, balls[1].body:pos():y())
   gc:drawPolyLine(sute)
   --]]
end
function pSpace:addConstraint(constraints)
   for _, v in ipairs(constraints)
      do self.space:addConstraint(v)
   end
end


--スペースを実体化する
space = pSpace(gravity)

-- 同じボールをいくつか実体化する
balls = {}
for i = 1, 7 do
   table.insert(balls, pBall(15, 7, 1, 0.5, color[math.random(#color)])) 
end
-- ボールをスペースへ投入する
for _, v in ipairs(balls) do
   space:addObj(v, 0, 0, math.random(W/2-5,W/2+5), math.random(H/2-5,H/2+5))
end

-- static なボールを 1 個作る
staticBall = pBall(LARGE, 10, 1, 0.8, 0xA8A8A8)
table.insert(balls, staticBall)
-- static なボールをスペースへ投入する
space:addStatic(staticBall, W/2, H/2)

-- 拘束条件を実体化する。全部のボールを互いにバネで拘束する。
-- DampedSpring(ボディー a, ボディー b, ボディー a のどこ、ボディー b のどこ, 自然長, ばね定数, 減衰係数)
constraints = {}
for i = 1, #balls do
   local j = i + 1; if i == #balls then j = 1 end;
   table.insert(constraints, physics.DampedSpring(balls[i].body, balls[j].body, ZERO, ZERO, 70, 400, 0))
end
-- 拘束条件をスペースへ投入する
space:addConstraint(constraints)

function on.paint(gc)
   space:paint(gc)
   gc:setColorRGB(0xB5B5B5)
   gc:drawString("acceleration of gravity: "..gravity.." [pixels/sec"..string.uchar(0x00B2).."]", 5, 0, "top")
end
function on.timer()
   space:step(dt)
   platform.window:invalidate()
end
function on.enterKey()
   timer.start(dt)
end