軌跡が描けるようにする。

-- 軌跡が描けるようにする。
-- オブジェクトごとに座標履歴を self として持っておく。
require "physics" require "color"

-----------------------
-- general functions --
-----------------------
function pV(x, y)  return physics.Vect(x, y) end
function sum(list) local result = ZERO for _, v in ipairs(list) do result = result + v end return result end
function centroid(vertsList) --[[ 多値 xg, yg で返す--]] local numVerts = #vertsList/2   local points = vertsList   points[#points+1] = vertsList[1]    points[#points+1] = vertsList[2]     local x, y = {}, {}   for i = 1, #points/2 do      x[i], y[i] = points[2*i-1], points[2*i]   end   local xc, yc, eachAreas = {}, {}, {}   local totalArea, xgTemp, ygTemp = 0, 0, 0   for i = 1, numVerts do      xc[i], yc[i] = (x[i] + x[i+1])/3, (y[i] + y[i+1])/3      eachAreas[i] = (x[i+1] * y[i] - y[i+1] * x[i])/2      totalArea = totalArea + eachAreas[i]      xgTemp, ygTemp = xgTemp + eachAreas[i] * xc[i], ygTemp + eachAreas[i] * yc[i]   end   return xgTemp/totalArea, ygTemp/totalArea; end
function compCentroid(vertsMat) --[[ 多値 xg, yg で返す --]]   function centroid2(vertsList) --[[ 多値 xg, yg, area で返す --]]      local numVerts = #vertsList/2      local points = vertsList      points[#points+1] = vertsList[1]      points[#points+1] = vertsList[2]      local x, y = {}, {}      for i = 1, #points/2 do         x[i], y[i] = points[2*i-1], points[2*i]      end      local xc, yc, eachAreas = {}, {}, {}      local totalArea, xgTemp, ygTemp = 0, 0, 0      for i = 1, numVerts do         xc[i], yc[i] = (x[i] + x[i+1])/3, (y[i] + y[i+1])/3         eachAreas[i] = (x[i+1] * y[i] - y[i+1] * x[i])/2         totalArea = totalArea + eachAreas[i]         xgTemp, ygTemp = xgTemp + eachAreas[i] * xc[i], ygTemp + eachAreas[i] * yc[i]      end      return xgTemp/totalArea, ygTemp/totalArea, totalArea   end   local xg, yg, area = {}, {}, {}   for i, v in ipairs(vertsMat) do      xg[i], yg[i], area[i] = centroid2(v)   end   local tempXg, tempYg, totalArea = 0, 0, 0   for i = 1, #xg do      tempXg, tempYg = tempXg + xg[i] * area[i], tempYg + yg[i] * area[i]      totalArea = totalArea + area[i]   end   return tempXg/totalArea, tempYg/totalArea; end
---------------------------------------------------
---------------------------------------------------

function on.resize()
   W, H = platform.window:width(), platform.window:height()
   X0, Y0, UNIT = W/2, H/2, math.floor(W/60)
end

platform.window:setBackgroundColor(color.white)
TICKING = false
GRID = true
drawShape = false
drawHistory, maxHistory, penHistory = true, 100, "thin"
spaceDamping = nil
VLimit = nil
ZERO = pV(0, 0)
LARGE = physics.misc.INFINITY()
dtLua = 1/(2^6)
dtChipmunk = 1/(2^6)
GRAVITY = -10
COLOR = { -- 参考: http://www.colordic.org
--[[パステルカラー--]]0xFF7F7F, 0xFF7FBF, 0xFF7FFF, 0xBF7FFF, 0x7F7FFF, 0x7FBFFF, 0x7FFFFF, 0x7FFFBF, 0x7FFF7F, 0xBFFF7F, 0xFFFF7F, 0xFFBF7F, 
--[[ビビッドカラー--]]0xFF6060, 0xFF60AF, 0xFF60FF, 0xAF60FF, 0x6060FF, 0x60AFFF, 0x60FFFF, 0x60FFAF, 0x60FF60, 0xAFFF60, 0xFFFF60, 0xFFAF60,
--[[モノトーン--]]0x5E5E5E, 0x7E7E7E, 0x9E9E9E, 0xBEBEBE, 0xDEDEDE, --0xFEFEFE, 
--[[メトロカラー--]]0x0078BA, 0x0079C2, 0x009944, 0x00A0DE, 0x00A7DB, 0x00ADA9, 0x019A66, 0x522886, 0x6CBB5A, 0x814721, 0x9B7CB6, 0x9CAEB7, 0xA9CC51, 0xB6007A, 0xBB641D, 0xD7C447, 0xE44D93, 0xE5171F, 0xE60012, 0xE85298, 0xEE7B1A, 0xF39700, 
}

-----------------
-- pBall class --
-----------------
pBall = class()
function pBall:init(mass, radius, elasticity, friction, color, group)
   self.color = color
   self.history = {} -- 座標履歴を self として持っておく。   
   local inertia = physics.misc.momentForCircle(mass, 0, radius, ZERO)
   self.body = physics.Body(mass, inertia)
   if VLimit then self.body:setVLimit(VLimit) end
   self.shape = physics.CircleShape(self.body, radius, ZERO)
      :setRestitution(elasticity)
      :setFriction(friction)
   if group then self.shape:setGroup(group) end
end
function pBall:paint(gc)
   local pos = self.body:pos()
   local cx, cy = X0 + UNIT * pos:x(), Y0 - UNIT * pos:y()
   local radius = UNIT * self.shape:radius()
   local diameter = radius + radius
   local angle = self.body:angle()
   local x0, y0 = cx - radius, cy - radius
   gc:setColorRGB(self.color)
   if drawHistory then
      --local pos = self.body:pos()
      --local cx, cy = X0 + UNIT * pos:x(), Y0 - UNIT * pos:y()
      gc:setPen(penHistory)
      self.history[#self.history+1] = cx
      self.history[#self.history+1] = cy
      if #self.history > maxHistory then table.remove(self.history, 1) table.remove(self.history, 1) end -- 座標履歴が一定数を超えたら古い座標から削除してゆく。
      gc:drawPolyLine(self.history)
   end
   gc:fillArc(x0, y0, diameter, diameter, 0, 360)
   gc:setPen("thin")
   gc:setColorRGB(color.black)
   if drawShape then
      gc:drawArc(x0, y0, diameter, diameter, 0, 360)
      gc:drawLine(cx, cy, cx + radius * math.cos(angle), cy - radius * math.sin(angle))
   end
end

----------------
-- pBox class --
----------------
pBox = class()
function pBox:init(mass, width, height, elasticity, friction, color, group)
   self.color = color
   self.history = {}
   local inertia = physics.misc.momentForBox(mass, width, height)
   self.body = physics.Body(mass, inertia)
   if VLimit then self.body:setVLimit(VLimit) end
   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)
      :setFriction(friction)
   if group then self.shape:setGroup(group) end
end
function pBox:paint(gc)
   gc:setColorRGB(self.color)
   if drawHistory then
      gc:setPen(penHistory)
      local pos = self.body:pos()
      local cx, cy = X0 + UNIT * pos:x(), Y0 - UNIT * pos:y()
      self.history[#self.history+1] = cx
      self.history[#self.history+1] = cy
      if #self.history > maxHistory then table.remove(self.history, 1) table.remove(self.history, 1) end
      gc:drawPolyLine(self.history)
   end
   local numVerts = self.shape:numVerts()
   local verts = {}
   local points = self.shape:points()
   for i = 1, numVerts do
      verts[#verts+1] = X0 + UNIT * points[i]:x()
      verts[#verts+1] = Y0 - UNIT * points[i]:y()
   end
   verts[#verts+1] = X0 + UNIT * points[1]:x()
   verts[#verts+1] = Y0 - UNIT * points[1]:y()
   gc:setPen("thin")
   gc:fillPolygon(verts)
   if drawShape then
      gc:setColorRGB(color.black)
      gc:drawPolyLine(verts)
   end
end

--------------------
-- pRegPoly class --
--------------------
pRegPoly = class(pBox)
function pRegPoly:init(mass, radius, numVerts, elasticity, friction, color, group)
   self.color = color
   self.history = {}
   local unitAngle = 2 * math.pi/numVerts
   local verts = {pV(radius, 0)}
   for i = 1, numVerts - 1 do
      verts[#verts+1] = pV(radius * math.cos(unitAngle * i), -radius * math.sin(unitAngle * i))
   end
   local inertia = physics.misc.momentForPoly(mass, verts, ZERO)
   self.body = physics.Body(mass, inertia)
   if VLimit then self.body:setVLimit(VLimit) end
   self.shape = physics.PolyShape(self.body, verts, ZERO)
      :setRestitution(elasticity)
      :setFriction(friction)
   if group then self.shape:setGroup(group) end
end

----------------------
-- pIrregPoly class --
----------------------
pIrregPoly = class(pBox)
function pIrregPoly:init(mass, vertsList, elasticity, friction, color, group)
   self.color = color
   self.history = {}
   local offsetX, offsetY = centroid(vertsList)
   local verts = {}
   for i = 1, #vertsList/2 do
      verts[i] = physics.Vect(vertsList[2*i-1] - offsetX, vertsList[2*i] - offsetY)
   end
   local inertia = physics.misc.momentForPoly(mass, verts, ZERO)
   self.body = physics.Body(mass, inertia)
   if VLimit then self.body:setVLimit(VLimit) end
   self.shape = physics.PolyShape(self.body, verts, ZERO)
      :setRestitution(elasticity)
      :setFriction(friction)
   if group then self.shape:setGroup(group) end
end

---------------------
-- pCompPoly class --
---------------------
pCompPoly = class()
function pCompPoly:init(mass, vertsMat, outersList, elasticity, friction, color, group)
   self.color = color
   self.history = {}
   local offsetX, offsetY = compCentroid(vertsMat)
   local outersVect = {}
   for i = 1, #outersList/2 do
      outersVect[i] = physics.Vect(outersList[2*i-1] - offsetX, outersList[2*i] - offsetY)
   end
   local verts = {}
   for i = 1, #vertsMat do 
      verts[i] = {}
      for j = 1, #vertsMat[i]/2 do
         verts[i][j] = physics.Vect(vertsMat[i][2*j-1] - offsetX, vertsMat[i][2*j] - offsetY)
      end
   end
   local inertia = physics.misc.momentForPoly(mass, outersVect, ZERO) 
   self.body = physics.Body(mass, inertia)
   if VLimit then self.body:setVLimit(VLimit) end
   self.shapesList = {}
   for i = 1, #vertsMat do
      self.shapesList[i] = physics.PolyShape(self.body, verts[i], ZERO)
         :setRestitution(elasticity)
         :setFriction(friction)
      if group then self.shapesList[i]:setGroup(group) end
   end
end
function pCompPoly:paint(gc)
   gc:setColorRGB(self.color)
   if drawHistory then
      gc:setPen(penHistory)
      local pos = self.body:pos()
      local cx, cy = X0 + UNIT * pos:x(), Y0 - UNIT * pos:y()
      self.history[#self.history+1] = cx
      self.history[#self.history+1] = cy
      if #self.history > maxHistory then table.remove(self.history, 1) table.remove(self.history, 1) end
      gc:drawPolyLine(self.history)
   end
   local points = {}
   local verts = {} 
   local numShepes = #self.shapesList
   gc:setPen("thin")
   for i = 1, numShepes do
      verts[i] = {}
      points = self.shapesList[i]:points()
      for j = 1, self.shapesList[i]:numVerts() do
         verts[i][#verts[i]+1] = X0 + UNIT * points[j]:x()
         verts[i][#verts[i]+1] = Y0 - UNIT * points[j]:y()
      end
      verts[i][#verts[i]+1] = X0 + UNIT * points[1]:x()
      verts[i][#verts[i]+1] = Y0 - UNIT * points[1]:y()
   end
   for _, v in ipairs(verts) do
      gc:fillPolygon(v)
   end
   if drawShape then
      gc:setColorRGB(color.black)
      for _, v in ipairs(verts) do
         gc:drawPolyLine(v)
      end
   end
end

----------------------
-- pStaticSeg class --
----------------------
pStaticSeg = class()
function pStaticSeg:init(x1, y1, x2, y2, radius, elasticity, friction, color, group)
   self.color = color
   local avec, bvec = pV(x1, y1),  pV(x2, y2)
   self.shape = physics.SegmentShape(nil, avec, bvec, radius)
      :setRestitution(elasticity)
      :setFriction(friction)
   if group then self.shape:setGroup(group) end
end
function pStaticSeg:paint(gc)
   local pos1 = self.shape:a()
   local x1, y1 = X0 + UNIT * pos1:x(), Y0 - UNIT * pos1:y()
   local pos2 = self.shape:b()
   local x2, y2 = X0 + UNIT * pos2:x(), Y0 - UNIT * pos2: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))
   if spaceDamping then self.space:setDamping(spaceDamping) end
   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)
   self.objects[#self.objects+1] = obj 
end
function pSpace:addCompPoly(obj, velx, vely, cx, cy)
   obj.body:setVel(pV(velx, vely))
      :setPos(pV(cx, cy))
   self.space:addBody(obj.body)
   for _, v in ipairs(obj.shapesList) do
      self.space:addShape(v)
   end
   self.objects[#self.objects+1] = obj
end
function pSpace:addStatic(obj, cx, cy)
   if obj.body then obj.body:setPos(pV(cx, cy)) end
   self.space:addStaticShape(obj.shape)
   self.objects[#self.objects+1] = obj
end
function pSpace:attractBetween()
   local forceTable = {}
   for r = 1, #self.objects do
      forceTable[r] = {}
      for c = 1, #self.objects do
         if r == c then
            forceTable[r][c] = ZERO
         elseif r > c then
            forceTable[r][c] = -forceTable[c][r]
         else
            local posA = self.objects[r].body:pos()
            local posB = self.objects[c].body:pos()
            local massA = self.objects[r].body:mass()
            local massB = self.objects[c].body:mass()
            local vectAB = posB - posA
            local lengthABsq = vectAB:lengthsq()
            local normAB = vectAB:normalize()
            forceTable[r][c] = normAB:mult(massA * massB/lengthABsq)
         end
      end
      self.objects[r].body:setForce(sum(forceTable[r]))
   end
end
function pSpace:paint(gc)
   gc:setColorRGB(color.gray)
   if GRAVITY ~= 0 then gc:drawString(string.format("acceleration of gravity: %3.1f", GRAVITY), 5, -3, "top") end
   if TICKING == true then gc:drawString("running", W - 70, 12, "top") end
   if TICKING == false then gc:drawString("stopped", W - 70, 12, "top") end
   gc:drawString(string.format("t = %4.1f", timeElapsed), W - 70, -3, "top")
   for _, v in ipairs(self.objects) do
      v:paint(gc)
   end  
end

----------
-- grid --
----------
function grid(gc)
   gc:setPen("thin")
   gc:setColorRGB(0x6495ED)
   gc:drawLine(0, Y0, W, Y0)
   gc:drawLine(X0, 0, X0, H)
   gc:fillPolygon({W, Y0, W - 7, Y0 - 4, W - 3, Y0, W - 7, Y0 + 4, W, Y0})
   gc:fillPolygon({X0, 0, X0 - 4, 7, X0, 3, X0 + 4, 7, X0, 0})
   gc:setColorRGB(0xCAE1FF)
   local i1 = 1; while Y0 - UNIT * i1 > 0 do gc:drawLine(0, Y0 - UNIT * i1, W, Y0 - UNIT * i1); i1 = i1 + 1 end
   local i2 = 1; while Y0 + UNIT * i2 < H do gc:drawLine(0, Y0 + UNIT * i2, W, Y0 + UNIT * i2); i2 = i2 + 1 end   
   local i3 = 1; while X0 + UNIT * i3 < W do gc:drawLine(X0 + UNIT * i3, 0, X0 + UNIT * i3, H); i3 = i3 + 1 end   
   local i4 = 1; while X0 - UNIT * i4 > 0 do gc:drawLine(X0 - UNIT * i4, 0, X0 - UNIT * i4, H); i4 = i4 + 1 end
   gc:setColorRGB(0x6495ED)
   gc:drawString("x", W - 10, Y0 - 21)
   gc:drawString("y", X0 + 5, -6)
end

-----------
-- fence --
-----------
function fence(x1, y1, x2, y2, radius, elasticity, friction)
   local color = color.black 
   local a1, a2, a3, a4 = ((x1 - UNIT * radius) - X0)/UNIT, (Y0 - (y1 - UNIT * radius))/UNIT, (Y0 - (y2 + UNIT * radius))/UNIT, ((x2 + UNIT * radius) - X0)/UNIT
   local walls = {
      pStaticSeg(a1, a2, a1, a3, radius, elasticity, friction, color),
      pStaticSeg(a1, a3, a4, a3, radius, elasticity, friction, color),
      pStaticSeg(a4, a3, a4, a2, radius, elasticity, friction, color),
      pStaticSeg(a4, a2, a1, a2, radius, elasticity, friction, color)
   }
   for _, v in ipairs(walls) do space:addStatic(v) end
end

--------------
-- polyLine --
--------------
function polyLine(xyList, radius, elasticity, friction, color)
   local lines = {}
   for i = 1, #xyList - 2, 2 do
      lines[i] = pStaticSeg(xyList[i], xyList[i+1], xyList[i+2], xyList[i+3], radius, elasticity, friction, color)
      space:addStatic(lines[i])
   end
end

------------------
-- 確かめてみる --
------------------
function reset()
   on.resize()
   timeElapsed = 0
   space = pSpace(GRAVITY)
   fence(0, 0, W, H, 100, 1, 0)
   polyLine({-12, -3, -8, -8, 8, -8, 12, -3}, 0.5, 1, 0, color.gray)
end   
function on.charIn(char)
   if char == "1" then
      poly = pCompPoly(10, var.recall("a"), var.recall("b"), 1, 0, COLOR[math.random(#COLOR)])
      space:addCompPoly(poly, 0, 0, 0, 10)
   elseif char == "2" then
      ball = pBall(5, 1.52, 1, 0, COLOR[math.random(#COLOR)])
      space:addObj(ball, 0, 0, 0, 10)
   elseif char == "3" then
      reg = pRegPoly(5, 0.3, math.random(3,6), 1, 0, COLOR[math.random(#COLOR)])
      space:addObj(reg, 0, 0, 0, 10)
   elseif char == "4" then
      poly = pIrregPoly(5, {math.random(-18,0)/10,0,0.6,1.2,1.4,-0.4,0.2,math.random(-28,-10)/10}, 1, 0, COLOR[math.random(#COLOR)])
      space:addObj(poly, 0, 0, 0, 10)
   elseif char == "5" then
      box = pBox(4, 0.2, 3, 1, 0, COLOR[math.random(#COLOR)])
      space:addObj(box, 0, 0, 0, 10)
   elseif char == "6" then
      poly = pCompPoly(10, var.recall("c"), var.recall("d"), 1, 0, COLOR[math.random(#COLOR)])
      space:addCompPoly(poly, 0, 0, 0, 10)
   end
   platform.window:invalidate()   
end
----------------------------------------------------------
----------------------------------------------------------

function on.construction() reset() end
function on.escapeKey()    reset() end
function on.paint(gc)
   if GRID then grid(gc) end
   space:paint(gc)
end
function on.timer()
   space:step(dtChipmunk)
   timeElapsed = timeElapsed + dtChipmunk
   platform.window:invalidate()
end
function on.enterKey()
   if     TICKING == false then timer.start(dtLua) TICKING = true
   elseif TICKING == true  then timer.stop()       TICKING = false
   end
end
a:=[[−2.7,0.448502757,−2.5,0.84920787,−2.5,−0.84920787,−2.7,−0.448502757][−2.5,0.84920787,−2.3,1.095971416,−2.3,−1.095971416,−2.5,−0.84920787][−2.3,1.095971416,−2.1,1.281074837,−2.1,−1.281074837,−2.3,−1.095971416][−2.1,1.281074837,−1.9,1.428688975,−1.9,−1.428688975,−2.1,−1.281074837][−1.9,1.428688975,−1.7,1.549565001,−1.7,−1.549565001,−1.9,−1.428688975][−1.7,1.549565001,−1.5,1.649591237,−1.5,−1.649591237,−1.7,−1.549565001][−1.5,1.649591237,−1.3,1.732383002,−1.3,−1.732383002,−1.5,−1.649591237][−1.3,1.732383002,−1.1,1.800319564,−1.1,−1.800319564,−1.3,−1.732383002][−1.1,1.800319564,−0.9,1.855033762,−0.9,−1.855033762,−1.1,−1.800319564][−0.9,1.855033762,−0.7,1.897669633,−0.7,−1.897669633,−0.9,−1.855033762][−0.7,1.897669633,−0.5,1.92902822,−0.5,−1.92902822,−0.7,−1.897669633][−0.5,1.92902822,−0.3,1.949653754,−0.3,−1.949653754,−0.5,−1.92902822][−0.3,1.949653754,−0.1,1.959885126,−0.1,−1.959885126,−0.3,−1.949653754][−0.1,1.959885126,0.1,1.959885126,0.1,−1.959885126,−0.1,−1.959885126][0.1,1.959885126,0.3,1.949653754,0.3,−1.949653754,0.1,−1.959885126][0.3,1.949653754,0.5,1.92902822,0.5,−1.92902822,0.3,−1.949653754][0.5,1.92902822,0.7,1.897669633,0.7,−1.897669633,0.5,−1.92902822][0.7,1.897669633,0.9,1.855033762,0.9,−1.855033762,0.7,−1.897669633][0.9,1.855033762,1.1,1.800319564,1.1,−1.800319564,0.9,−1.855033762][1.1,1.800319564,1.3,1.732383002,1.3,−1.732383002,1.1,−1.800319564][1.3,1.732383002,1.5,1.649591237,1.5,−1.649591237,1.3,−1.732383002][1.5,1.649591237,1.7,1.549565001,1.7,−1.549565001,1.5,−1.649591237][1.7,1.549565001,1.9,1.428688975,1.9,−1.428688975,1.7,−1.549565001][1.9,1.428688975,2.1,1.281074837,2.1,−1.281074837,1.9,−1.428688975][2.1,1.281074837,2.3,1.095971416,2.3,−1.095971416,2.1,−1.281074837][2.3,1.095971416,2.5,0.84920787,2.5,−0.84920787,2.3,−1.095971416][2.5,0.84920787,2.7,0.448502757,2.7,−0.448502757,2.5,−0.84920787]]
b:={0.448502757,0.84920787,1.095971416,1.281074837,1.428688975,1.549565001,1.649591237,1.732383002,1.800319564,1.855033762,1.897669633,1.92902822,1.949653754,1.959885126,1.959885126,1.949653754,1.92902822,1.897669633,1.855033762,1.800319564,1.732383002,1.649591237,1.549565001,1.428688975,1.281074837,1.095971416,0.84920787,0.448502757,−0.448502757,−0.84920787,−1.095971416,−1.281074837,−1.428688975,−1.549565001,−1.649591237,−1.732383002,−1.800319564,−1.855033762,−1.897669633,−1.92902822,−1.949653754,−1.959885126,−1.959885126,−1.949653754,−1.92902822,−1.897669633,−1.855033762,−1.800319564,−1.732383002,−1.649591237,−1.549565001,−1.428688975,−1.281074837,−1.095971416,−0.84920787,−0.448502757}
c:=[[1.41,1.41,2.12,2.12,3,0,2,0][2,0,3,0,2.12,−2.12,1.41,−1.41][1.41,−1.41,2.12,−2.12,0,−3,0,−2][0,−2,0,−3,−2.12,−2.12,−1.41,−1.41][−1.41,−1.41,−2.12,−2.12,−3,0,−2,0][−2,0,−3,0,−2.12,2.12,−1.41,1.41]]
d:={2.12,2.12,3,0,2.12,−2.12,0,−3,−2.12,−2.12,−3,0,−2.12,2.12,−1.41,1.41,−2,0,−1.41,−1.41,0,−2,1.41,−1.41,2,0,1.41,1.41}