Calculator Community > Lua

Chipmunk Physics

(1/4) > >>

Jim Bauwens:
Hi all.

3.2 integrates the Chipmunk physics engine with Lua bindings. This allows us create fun games with the physics.
However there is just one problem. It's pretty complex.
So here is some useful info if you want to start programming.

1. The API documentation for 3.2
It's a must have in order to find all the API function names (duh :P )

2. The online chipmunk docs
http://chipmunk-physics.net/release/Chipmunk-5.x/Chipmunk-5.3.4-Docs/
Even though it describes the C API, it shows how the concepts of the engine and how it's structured.
And most Lua functions have similar names to the C ones.
You will find yourself checking this resource often when you start programming with the engine ;)

3. A tutorial (although for the C API)
http://www.alexandre-gomes.com/articles/chipmunk/
This tutorial helps you understand the concepts and inner workings of the physics engine.

4. Example code (by me)
Spoiler For Basic start:

platform.apilevel = '2.0'
require 'physics'

LARGE=physics.misc.INFINITY()
ZERO =physics.Vect(0,0)
------------------
-- Space Object --
------------------

pSpace   = class()

function pSpace:init(gravity)
self.space = physics.Space()
:setGravity(physics.Vect(0, gravity))
:setSleepTimeThreshold(.5)

self.objects   = {}
end

function pSpace:step(n)
self.space:step(n)
end

obj.body:setPos(physics.Vect(x, y))

table.insert(self.objects, obj)
return self
end

obj.body:setPos(physics.Vect(x, y))

for _, ss in ipairs(obj.segments) do
end

table.insert(self.objects, obj)
return self
end

obj.body:setPos(physics.Vect(x, y))

table.insert(self.objects, obj)
return self
end

function pSpace:paint(gc)
for _, obj in ipairs(self.objects) do
obj:paint(gc)
end
end

----------------
-- Box Object --
----------------

pBox   = class()

function pBox:init(w,h,mass,color)
self.h=h
self.w=w
self.mass=mass
self.color=color

self.verts = {
physics.Vect(-w/2, -h/2),
physics.Vect(-w/2,  h/2),
physics.Vect(w/2 ,  h/2),
physics.Vect(w/2 , -h/2)
}

self.body   = physics.Body(1, 1)
self.body:setMass(mass)
self.body:setMoment(physics.misc.momentForPoly(mass, self.verts, ZERO))
self.body:setVel(ZERO)

self.shape = physics.PolyShape(self.body, self.verts, ZERO)

self.shape:setRestitution(0.6)
self.shape:setFriction(0.6)
end

function pBox:paint(gc)
local coords   = self.shape:points()
local polycoords   = {}
for k, vert in ipairs(coords) do
table.insert(polycoords, vert:x())
table.insert(polycoords, vert:y())
end
local x,y=coords[1]:x(),coords[1]:y()
table.insert(polycoords, x)
table.insert(polycoords, y)

gc:setColorRGB(self.color)
gc:fillPolygon(polycoords)
gc:setColorRGB(0,0,0)
gc:drawPolyLine(polycoords)
end

-----------------
-- Ball Object --
-----------------

pBall   = class()

function pBall:init(r,mass,color)
self.r=r
self.mass=mass
self.color=color

self.body   = physics.Body(1, 1)
self.body:setMass(mass)
self.body:setMoment(physics.misc.momentForCircle(mass, 0, r, ZERO))
self.body:setVel(ZERO)

self.shape = physics.CircleShape(self.body, r, ZERO)

self.shape:setRestitution(0.6)
self.shape:setFriction(0.6)
--[[
self.segment = physics.SegmentShape(self.body, physics.Vect(0,-r), physics.Vect(0,0), 1)
self.segment:setRestitution(0)
self.segment:setFriction(0)
--]]
end

end

function pBall:paint(gc)
local pos=self.body:pos()
local x, y = pos:x(), pos:y()
local r = self.r

gc:setColorRGB(self.color)
gc:fillArc(x-r, y-r, 2*r+1, 2*r+1, 0, 360)
gc:setColorRGB(0)
gc:drawArc(x-r, y-r, 2*r, 2*r, 0, 360)

local x2,y2
local angle=self.body:angle()
x2   = math.cos(angle)*self.r+x
y2   = math.sin(angle)*self.r+y
gc:drawLine(x,y,x2,y2)
end

------------------
-- Terrain code --
------------------

function getMidPoint(line)
local xdif    = math.abs(line[3]-line[1])
local ydif    = math.abs(line[4]-line[2])
local x    = math.min(line[3], line[1]) + xdif/2
local y    = math.min(line[4], line[2]) + ydif/2
return x, y
end

function terrain(bline, range, roughness, tms)
local lines    = {bline}
local lines2    = {}

local midX, midY

for times=1, tms do
for index, line in pairs(lines) do
midX, midY    = getMidPoint(line)
midY    = midY + math.random(-range, range)

table.insert(lines2, {line[1], line[2], midX, midY})
table.insert(lines2, {midX, midY, line[3], line[4]})
end
lines    = lines2
lines2    = {}
range    = range * (roughness*2^-roughness)
end

return lines
end

------------------------
-- Terrain to physics --
------------------------

pTerrain   = class()

function pTerrain:init(bline, range, roughness, times)
self.lines    = terrain(bline, range, roughness, times)
self.segments = {}

self.mass   = LARGE
self.body   = physics.Body(1, 1)
self.body:setMass(self.mass)

self.inertia   = 0

local a, b, ss
for index, line in ipairs(self.lines) do
a   = physics.Vect(line[1], line[2])
b   = physics.Vect(line[3], line[4])

self.inertia   = self.inertia + physics.misc.momentForSegment(self.mass/#self.lines, a, b)
ss   = physics.SegmentShape(self.body, a, b, 1)
ss:setRestitution(0)
ss:setFriction(1)
table.insert(self.segments, ss)
end

self.body:setMoment(self.inertia)
self.body:setVel(ZERO)

end

function pTerrain:paint(gc)
local a,b
for index, ss in ipairs(self.segments) do
a = ss:a()
b = ss:b()
gc:drawLine(a:x(), a:y(), b:x(), b:y())
end
end

function on.construction()
space   = pSpace(9. 8)

timer.start(0.01)
count=0

ter   = pTerrain({0,120,318,120}, 60, 0.6, 5)

ball = pBall(10, 5, 0xaaffaa)
end

function physicsUpdate()
space:step(0.1)

count = count + 1
if count == 2 then
platform.window:invalidate()
count = 1
end
end

py,px=100,160

function on.paint(gc)
space:paint(gc)
gc:fillRect(px, py, 4, 4)
end

kaction   = {up={0,-5}, down={0,5}, left={-5,0}, right={5,0}}
function on.arrowKey(key)
px=px+kaction[1]
py=py+kaction[2]
end

function on.enterKey()
local box   = pBox(20, 20, 5, math.random(2^16))
end

function on.timer()
physicsUpdate()
end

Spoiler For A bit more advanced (motors/springs/etc):

platform.apilevel = '2.0'
require 'physics'

LARGE=physics.misc.INFINITY()
ZERO =physics.Vect(0,0)
------------------
-- Space Object --
------------------

pSpace   = class()

function pSpace:init(gravity)
self.space = physics.Space()
:setGravity(physics.Vect(0, gravity))
:setSleepTimeThreshold(.5)
--:resizeActiveHash(30, 500)
self.objects   = {}
end

function pSpace:step(n)
self.space:step(n)
end

obj.body:setPos(physics.Vect(x, y))

table.insert(self.objects, obj)
return self
end

obj.body:setPos(physics.Vect(x, y))

for _, ss in ipairs(obj.segments) do
end

table.insert(self.objects, obj)
return self
end

obj.body:setPos(physics.Vect(x, y))

table.insert(self.objects, obj)
return self
end

for _, childObj in ipairs(obj.objects) do
end

for _, constraint in ipairs(obj.constraints) do
end

table.insert(self.objects, obj)
return self
end

function pSpace:paint(gc)
for _, obj in ipairs(self.objects) do
obj:paint(gc)
end
end

----------------
-- Box Object --
----------------

pBox   = class()

function pBox:init(w, h, mass, color, f, e, group)
self.h=h
self.w=w
self.mass=mass
self.color=color

self.verts = {
physics.Vect(-w/2, -h/2),
physics.Vect(-w/2,  h/2),
physics.Vect(w/2 ,  h/2),
physics.Vect(w/2 , -h/2)
}

self.body   = physics.Body(1, 1)
self.body:setMass(mass)
self.body:setMoment(physics.misc.momentForPoly(mass, self.verts, ZERO))
self.body:setVel(ZERO)

self.shape = physics.PolyShape(self.body, self.verts, ZERO)

self.shape:setRestitution(e or 0.6)
self.shape:setFriction(f or 0.6)

if group then self.shape:setGroup(group) end
end

function pBox:paint(gc)
local coords   = self.shape:points()
local polycoords   = {}
for k, vert in ipairs(coords) do
table.insert(polycoords, vert:x())
table.insert(polycoords, vert:y())
end
local x,y=coords[1]:x(),coords[1]:y()
table.insert(polycoords, x)
table.insert(polycoords, y)

gc:setColorRGB(self.color)
gc:fillPolygon(polycoords)
gc:setColorRGB(0,0,0)
gc:drawPolyLine(polycoords)
end

-----------------
-- Ball Object --
-----------------

pBall   = class()

function pBall:init(r,mass,color, f, e, group)
self.r=r
self.mass=mass
self.color=color

self.body   = physics.Body(1, 1)
self.body:setMass(mass)
self.body:setMoment(physics.misc.momentForCircle(mass, 0, r, ZERO))
self.body:setVel(ZERO)

self.shape = physics.CircleShape(self.body, r, ZERO)

self.shape:setRestitution(e or 0.6)
self.shape:setFriction(f or 0.6)

if group then self.shape:setGroup(group) end
end

function pBall:paint(gc)
local pos=self.body:pos()
local x, y = pos:x(), pos:y()
local r = self.r

gc:setColorRGB(self.color)
gc:fillArc(x-r, y-r, 2*r+1, 2*r+1, 0, 360)
gc:setColorRGB(0)
gc:drawArc(x-r, y-r, 2*r, 2*r, 0, 360)

local x2,y2
local angle=self.body:angle()
x2   = math.cos(angle)*self.r+x
y2   = math.sin(angle)*self.r+y
gc:drawLine(x,y,x2,y2)
end

pCar   = class()

function pCar:init()
local group   = 5

local wheelR   = 5   -- wheel Radius
local wheelM   = 5   -- wheel Mass
local wheelF   = 0.9   -- wheel Friction
local wheelE   = 0.0   -- wheel Elasticity

local chassisM   = 10   -- Chassis Mass
local chassisW   = 24   -- Chassis Width
local chassisH   = 10   -- Chassis Height
local chassisF   = 0.7   -- Chassis Friction
local chassisE   = 0.0   -- Chassis Elasticity

self.wheel1   = pBall(wheelR, wheelM, 0x55CC55, wheelF, wheelE, group)
self.wheel2   = pBall(wheelR, wheelM+8, 0x55CC55, wheelF, wheelE, group)

self.chassis   = pBox(chassisW, chassisH, chassisM, 0x5555CC, chassisF, chassisE, group)

self.joint1   = physics.GrooveJoint(self.chassis.body, self.wheel1.body, physics.Vect(-10, 5), physics.Vect(-10, 25), ZERO)
self.joint2   = physics.GrooveJoint(self.chassis.body, self.wheel2.body, physics.Vect( 10, 5), physics.Vect( 10, 25), ZERO)

self.spring1   = physics.DampedSpring(self.chassis.body, self.wheel1.body, physics.Vect(-10, 0), ZERO, 17, 10, 10)
self.spring2   = physics.DampedSpring(self.chassis.body, self.wheel2.body, physics.Vect( 10, 0), ZERO, 17, 10, 10)

self.Fmotor   = physics.SimpleMotor(self.chassis.body, self.wheel2.body, 0):setMaxForce(1000)
self.Pmotor   = physics.SimpleMotor(self.chassis.body, self.wheel2.body, 0):setMaxForce(1000)
self.gear   = physics.GearJoint(self.wheel1.body, self.wheel2.body, 0, 1)

self.objects   = {
{obj = self.wheel1 , x = -10 ,y = 7},
{obj = self.wheel2 , x = 10  , y = 7},
{obj = self.chassis, x = 0 ,y = -7},
}

self.constraints   = {self.joint1, self.joint2, self.spring1, self.spring2, self.Fmotor, self.Pmotor, self.gear}
end

function pCar:paint(gc)
self.wheel1:paint(gc)
self.wheel2:paint(gc)
self.chassis:paint(gc)
end

------------------
-- Terrain code --
------------------

function getMidPoint(line)
local xdif    = math.abs(line[3]-line[1])
local ydif    = math.abs(line[4]-line[2])
local x    = math.min(line[3], line[1]) + xdif/2
local y    = math.min(line[4], line[2]) + ydif/2
return x, y
end

function terrain(bline, range, roughness, tms)
local lines    = {bline}
local lines2    = {}

local midX, midY

for times=1, tms do
for index, line in pairs(lines) do
midX, midY    = getMidPoint(line)
midY    = midY + math.random(-range, range)

table.insert(lines2, {line[1], line[2], midX, midY})
table.insert(lines2, {midX, midY, line[3], line[4]})
end
lines    = lines2
lines2    = {}
range    = range * (roughness*2^-roughness)
end

return lines
end

------------------------
-- Terrain to physics --
------------------------

pTerrain   = class()

function pTerrain:init(bline, range, roughness, times)
self.lines    = terrain(bline, range, roughness, times)
self.segments = {}

self.mass   = LARGE
self.body   = physics.Body(1, 1)
self.body:setMass(self.mass)

self.inertia   = 0

local a, b, ss
for index, line in ipairs(self.lines) do
a   = physics.Vect(line[1], line[2])
b   = physics.Vect(line[3], line[4])

self.inertia   = self.inertia + physics.misc.momentForSegment(self.mass/#self.lines, a, b)
ss   = physics.SegmentShape(self.body, a, b, 1)
ss:setRestitution(0)
ss:setFriction(1)
table.insert(self.segments, ss)
end

self.body:setMoment(self.inertia)
self.body:setVel(ZERO)

end

function pTerrain:paint(gc)
local a,b
for index, ss in ipairs(self.segments) do
a = ss:a()
b = ss:b()
gc:drawLine(a:x(), a:y(), b:x(), b:y())
end
end

function on.construction()
space   = pSpace(9. 8)

timer.start(0.02)
count=0

--[[
floor   = pBox(300, 20, LARGE, 0xCC5555, 0.9)
--]]

ter   = pTerrain({0,120,318,120}, 60, 0.6, 5)

car   = pCar()
end

function on.tabKey()
car.Pmotor:setMaxForce(10000)
car.Pmotor:setRate(car.Pmotor:rate()-0.3)
end

function on.escapeKey()
car.Pmotor:setMaxForce(0)
car.Pmotor:setRate(0)
end

function physicsUpdate()
space:step(0.1)

count = count + 1
if count == 2 then
platform.window:invalidate()
count = 1
end
end

py,px=100,160

function on.paint(gc)
local f   = math.abs(car.Pmotor:rate())
gc:drawString("Force: " .. f, 2, 2, "top")
space:paint(gc)
gc:fillRect(px, py, 4, 4)
end

kaction   = {up={0,-5}, down={0,5}, left={-5,0}, right={5,0}}
function on.arrowKey(key)
px=px+kaction[1]
py=py+kaction[2]
end

function on.enterKey()
local box   = pBox(20, 20, 5, math.random(2^16))
end

function on.timer()
physicsUpdate()
end

I spend some time making this code, so please take care of it  ;)

5. Take a look at other exisiting activities for the TI-Nspire that use the physics engine
http://education.ti.com/calculators/tisciencenspired/US/Activities/Detail?sa=5029&id=17888

And last but not least ...
Have fun !

leafy:
Wow, that's fantastic! I still find it amazing that Chipmunk can even run on a calculator, so this is all very cool stuff :D I'm more familiar with Box2D myself, but I've heard that Chipmunk is better at handling large numbers of objects, so it should be interesting to see what people can come up with.

Jim Bauwens:
Yeah, it's indeed a very nice addition :)

However, don't expect super complex and graphically advanced games to be possible :P
It's still a calculator, and the Lua bindings are of course a bit slower than the C one. (This is why Ndless 3.2 will be awesome :D).
But a (good functioning) Angry Birds clone should be possible.

3rik:
Thanks for gathering these resources!  :)

space = pSpace(9.8)

got turned into

space = pSpace(9.8)

cyanophycean314:
I'm not sure if I'm going to give this a try in my programs, but it is interesting.

--- Quote from: 3rik on July 26, 2012, 04:36:17 pm ---Thanks for gathering these resources!  :)

space = pSpace(9.8)

got turned into

space = pSpace(9.8)