物理エンジンを使う 12 of 12, 何かを組み合わせたものをどこかにたくさん実体化する

-- 物理エンジンを使う 12 of 12, 何かを組み合わせたものをどこかにたくさん実体化する 
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 = 100
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:setPen("thin")
   gc:drawArc(x0, y0, diameter, diameter, 0, 360)
   gc:drawLine(cx, cy, radius * math.cos(angle) + cx, radius * math.sin(angle) + cy)
end

----------------
-- pBox class --
----------------
pBox = class()
function pBox:init(mass, width, height, elasticity, friction, color, group)
   self.color = color or 0
   local inertia = physics.misc.momentForBox(mass, width, height)
   self.body = physics.Body(mass, inertia)
   local verts = {
      pV(-width/2, -height/2),
      pV(-width/2,  height/2),
      pV( width/2,  height/2),
      pV( width/2, -height/2)
   }
   self.shape = physics.PolyShape(self.body, verts, ZERO)
      :setRestitution(elasticity or 0.8)
      :setFriction(friction or 0.8)
      if group then self.shape:setGroup(group) end
end
function pBox:paint(gc)
   local numVerts = self.shape:numVerts()
   local verts = {}
   for i = 1, numVerts + 1 do
      local j = i; if i > numVerts then j = 1 end
      table.insert(verts, self.shape:points()[j]:x())
      table.insert(verts, self.shape:points()[j]:y())
   end
   gc:setColorRGB(self.color)
   gc:fillPolygon(verts)
   gc:setPen("thin")
   gc:setColorRGB(0)
   gc:drawPolyLine(verts)
end

----------------------
-- pStaticSeg class --
----------------------
pStaticSeg = class()
function pStaticSeg:init(x1, y1, x2, y2, elasticity, friction, color, group)
   self.color = color or 0
   local avec, bvec = pV(x1, y1),  pV(x2, y2)
   self.shape = physics.SegmentShape(nil, avec, bvec, 2.5)
      :setRestitution(elasticity or 0.8)
      :setFriction(friction or 0.8)
      if group then self.shape:setGroup(group) end
end
function pStaticSeg:paint(gc)
   local x1, y1 = self.shape:a():x(), self.shape:a():y()
   local x2, y2 = self.shape:b():x(), self.shape:b():y()
   gc:setColorRGB(self.color)
   gc:setPen("thick")
   gc:drawLine(x1, y1, x2, y2)
end

------------------
-- pSpace class --
------------------
pSpace = class()
function pSpace:init(gravity)
   self.space = physics.Space()
      :setGravity(pV(0, gravity))
      --:setIterations(999)
      --:setElasticIterations(999)
   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 or 0, vely or 0))
      :setPos(pV(cx or W/2, cy or H/2))
   self.space
      :addBody(obj.body)
      :addShape(obj.shape)
   table.insert(self.objects, obj)
end
function pSpace:addStatic(obj, cx, cy)
   if obj.body then obj.body:setPos(pV(cx or W/2, cy or H/2)) end
   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
end
function pSpace:addConstraint(jointsList)
   for _, v in pairs(jointsList) do
      self.space:addConstraint(v)
   end
end
function on.paint(gc)
   space:paint(gc)
   gc:setColorRGB(0xB5B5B5)
   gc:drawString(string.format("acceleration of gravity: %d [pixels/sec"..string.uchar(0x00B2).."]", GRAVITY), 5, 0, "top")
end
function on.timer()
   space:step(DT)
   platform.window:invalidate()
end
function on.enterKey()
   timer.start(DT)
end

function bike(velx, vely, cx, cy, group)
   local frameW, frameH = 15, 2
   local wheelR = 5
      
   local frame = pBox(1, frameW, frameH, 0.3, 0.8, COLOR[math.random(#COLOR)], group)
   local frontWheel = pBall(1, wheelR, 0.3, 0.8, COLOR[math.random(#COLOR)], group)
   local rearWheel = pBall(1, wheelR, 0.8, 0.8, COLOR[math.random(#COLOR)], group)
   
   space:addObj(frame, velx, vely, cx, cy)
   space:addObj(frontWheel, velx, vely, cx + frameW/2, cy + frameH/2)
   space:addObj(rearWheel, velx, vely, cx - frameW/2, cy + frameH/2)
   
   local jointsList = {
      joint1 = physics.PinJoint(frame.body, frontWheel.body, pV(frameW/2, frameH/2), ZERO),
      joint2 = physics.PinJoint(frame.body, rearWheel.body, pV(-frameW/2, frameH/2), ZERO),
   }
   space:addConstraint(jointsList)
end


----------------------------------------------------------
----------------------------------------------------------
space = pSpace(GRAVITY)
function on.mouseDown(cx, cy)
   bike(0, 0, cx, cy, math.random(999999))
   platform.window:invalidate()
end
floor1 = pStaticSeg(10,50,100,150)
floor2 = pStaticSeg(100,150,250,150)
space:addStatic(floor1)
space:addStatic(floor2)
----------------------------------------------------------
----------------------------------------------------------