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
Download it at http://education.ti.com/nspire/scripting-api .
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

function pSpace:addObj(obj, x, y)
   obj.body:setPos(physics.Vect(x, y))
   self.space:addBody(obj.body)
   
   self.space:addShape(obj.shape)
   if obj.added then obj:added(self) end
   
   table.insert(self.objects, obj)
   return self
end

function pSpace:addTerrainObj(obj, x, y)
   obj.body:setPos(physics.Vect(x, y))
   --self.space:addBody(obj.body)
   
   for _, ss in ipairs(obj.segments) do
      self.space:addStaticShape(ss)
   end
   
   table.insert(self.objects, obj)
   return self
end

function pSpace:addStaticObj(obj, x, y)
   obj.body:setPos(physics.Vect(x, y))
   --self.space:addBody(obj.body)
   self.space:addStaticShape(obj.shape)
   
   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

function pBall:added(spaceObj)
   --spaceObj.space:addShape(self.segment)
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)
   space:addTerrainObj(ter, 0,0)
   
   ball = pBall(10, 5, 0xaaffaa)
   space:addObj(ball, 160,10)
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))
   space:addObj(box, px, py)
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

function pSpace:addObj(obj, x, y)
   obj.body:setPos(physics.Vect(x, y))
   self.space:addBody(obj.body)
   
   self.space:addShape(obj.shape)
   if obj.added then obj:added(self) end
   
   table.insert(self.objects, obj)
   return self
end

function pSpace:addTerrainObj(obj, x, y)
   obj.body:setPos(physics.Vect(x, y))
   --self.space:addBody(obj.body)
   
   for _, ss in ipairs(obj.segments) do
      self.space:addStaticShape(ss)
   end
   
   table.insert(self.objects, obj)
   return self
end

function pSpace:addStaticObj(obj, x, y)
   obj.body:setPos(physics.Vect(x, y))
   --self.space:addBody(obj.body)
   self.space:addStaticShape(obj.shape)
   
   table.insert(self.objects, obj)
   return self
end

function pSpace:addComplexObj(obj, x, y)
   for _, childObj in ipairs(obj.objects) do
      self:addObj(childObj.obj, x+childObj.x, y+childObj.y)
   end
   
   for _, constraint in ipairs(obj.constraints) do
      self.space:addConstraint(constraint)
   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)
   space:addStaticObj(floor, 150, 200)
   --]]
   
   ter   = pTerrain({0,120,318,120}, 60, 0.6, 5)
   space:addTerrainObj(ter, 0,0)
   
   car   = pCar()
   space:addComplexObj(car, 50, 50)
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))
   space:addObj(box, px, py)
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)

in your code.

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)

in your code.

--- End quote ---

Yeah, I saw that.  8)

Navigation

[0] Message Index

[#] Next page

Go to full version