頂点の凹んだ多角形を作る
頂点の凹んだ多角形は、1 個の多角形としては作れないので、1 つのボディに複数のシェイプを貼り付けて作る。
-- 頂点の凹んだ多角形が作れるようにする。 -- 1 つのボディに複数のシェイプを貼り付ける。 -- pCompPoly クラスと pSpace:addCompPloy メソッドとを作った。 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 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 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) gc:fillArc(x0, y0, diameter, diameter, 0, 360) gc:setColorRGB(color.black) gc:setPen("thin") gc:drawArc(x0, y0, diameter, diameter, 0, 360) gc:drawLine(cx, cy, cx + radius * math.cos(angle), cy - radius * math.sin(angle)) end ---------------- -- pBox class -- ---------------- pBox = class() function pBox:init(mass, width, height, elasticity, friction, color, group) self.color = color 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) 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:setColorRGB(self.color) gc:fillPolygon(verts) gc:setPen("thin") gc:setColorRGB(color.black) gc:drawPolyLine(verts) end -------------------- -- pRegPoly class -- -------------------- pRegPoly = class(pBox) function pRegPoly:init(mass, radius, numVerts, elasticity, friction, color, group) self.color = color 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 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 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 -- #vertsMat は個別多角形の個数。 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) local points = {} -- 頂点座標(ベクトル)を一時的に入れておくリスト(ループするたびに書き換わる)。 local verts = {} -- 頂点座標(xy 座標)を入れる行列。 local numShepes = #self.shapesList 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() -- xy 座標に変換する。 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 gc:setColorRGB(self.color) for _, v in ipairs(verts) do gc:fillPolygon(v) end gc:setPen("thin") gc:setColorRGB(color.black) for _, v in ipairs(verts) do gc:drawPolyLine(v) 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) -- スクリーン座標(左上 x, y, 右下 x, y, 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) -- ({x1, y1, x2, y2, ..., xn, yn}, 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, 1) -- fence はスクリーン座標(左上 x, y, 右下 x, y, radius, elasticity, friction)で指定する。すり抜けないように極端に分厚くしておく。 polyLine({-12, -3, -8, -8, 8, -8, 12, -3}, 0.25, 1, 0, color.gray) -- ({x1, y1, x2, y2, ..., xn, yn}, radius, elasticity, friction, color) end function on.charIn(char) if char == " " then --(質量, 頂点行列, 外周座標, ~) --poly = pCompPoly(10, {{4,3,4.59,0.658,-0.591,-0.636,-1.18,1.71},{-2,5,-1,1,-2.17,0.708,-3.17,4.71}}, {4,3,4.59,0.658,-0.591,-0.636,-1,1,-2.17,0.708,-3.17,4.71,-2,5,-1.18,1.71}, 1, 0, COLOR[math.random(#COLOR)]) poly = pCompPoly(10, var.recall("a"), var.recall("b"), 1, 0, COLOR[math.random(#COLOR)]) space:addCompPoly(poly, 0, 0, 0, 10) end if char == "1" then ball = pBall(0.5, 0.6, 1, 0, COLOR[math.random(#COLOR)]) space:addObj(ball, 0, 0, 0, 10) end if char == "2" then reg = pRegPoly(0.5, 0.8, math.random(3,6), 1, 0, COLOR[math.random(#COLOR)], group) space:addObj(reg, 0, 0, 0, 10) end if char == "3" then poly = pIrregPoly(1, {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) 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