require "physics" require "color"
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) local numVerts = #vertsList/2 local points = vertsList table.insert(points, vertsList[1]); table.insert(points, 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 physics.Vect(xgTemp/totalArea, ygTemp/totalArea) end
function on.resize()
W, H = platform.window:width(), platform.window:height()
X0, Y0, UNIT = W/2, H/2, math.floor(W/35)
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 = {
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()
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()
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 + 1 do
local j = i; if i > numVerts then j = 1 end
table.insert(verts, X0 + UNIT * points[j]:x())
table.insert(verts, Y0 - UNIT * points[j]:y())
end
gc:setColorRGB(self.color)
gc:fillPolygon(verts)
gc:setPen("thin")
gc:setColorRGB(color.black)
gc:drawPolyLine(verts)
end
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
table.insert(verts, 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(pBox)
function pIrregPoly:init(mass, vertsList, elasticity, friction, color, group)
self.color = color
local verts = {}
for i = 1, #vertsList/2 do
verts[i] = physics.Vect(vertsList[2*i-1], vertsList[2*i])
end
local offset = -centroid(vertsList)
local inertia = physics.misc.momentForPoly(mass, verts, offset)
self.body = physics.Body(mass, inertia)
if VLimit then self.body:setVLimit(VLimit) end
self.shape = physics.PolyShape(self.body, verts, offset)
:setRestitution(elasticity)
:setFriction(friction)
if group then self.shape:setGroup(group) end
end
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("thin")
gc:drawLine(x1, y1, x2, y2)
end
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)
table.insert(self.objects, obj)
end
function pSpace:addStatic(obj, cx, cy)
if obj.body then obj.body:setPos(pV(cx, cy)) end
self.space:addStaticShape(obj.shape)
table.insert(self.objects, 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
gc:drawString(string.format("t = %5.1f", timeElapsed), W - 70, -3, "top")
for _, v in ipairs(self.objects) do
v:paint(gc)
end
end
Grid = class()
function Grid:init() end
function Grid:paint(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
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
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
if GRID then grid = Grid() end
space = pSpace(GRAVITY)
fence(0, 0, W, H, 100, 1, 1)
polyLine({-12, -3, -8, -8, 8, -8, 12, -3}, 0.1, 1, 0, color.gray)
end
function on.charIn(char)
if char == " " then
poly = pIrregPoly(1, {1.2,1,1.4,0.6,0.2,math.random(-28,4)/10,0,1,0.6,1.2}, 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:paint(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