Basic physics

Based on the scene created earlier, let's add some physical behavior to our scene.

Step 1: Using ODE to simulate movement

ODE is the physics library that luxinia integrates. We can use it do move the object with a simulated physical behaviour. We need therefore a dbody object, which represents a physically simulated object, and a stepping function that runs the simulation. The dbody is created next to the actor, and the actor is linked to it

actor = actornode.new("actor",0,0,0)
actor.l3d = l3dprimitive.newsphere("box",1,1,1)
actor.l3d:linkinterface(actor)
actor.l3d:rfLitSun(true)
actor.body = dbody.new()
actor.body:masssphere(1,1)
actor:link(actor.body)

We also need to assign a massproperty to the body.

We need now to modify our think function in order to handle use the physic simulation:

function think ()
  local left = Keyboard.isKeyDown("LEFT")
  local right = Keyboard.isKeyDown("RIGHT")
  local up = Keyboard.isKeyDown("UP")
  local down = Keyboard.isKeyDown("DOWN")
  local fx = (left and 1 or 0) - (right and 1 or 0)
  local fy = (up and 1 or 0) - (down and 1 or 0)
  -- fx,fy: movement directions

  actor.body:addforce(fx*.01,fy*.01,0)
  dworld.quickstep(1)
end

The quickstep function will simulate now our movement, while we can apply the force by the given user input.

Step 2: Collision feedback

The simulation itself is only handling the movement of bodies. If we want to interact with a physical environment, we need collision feedback. Therefore, we need some geometry in our scene. We won't see it, but if we move our sphere around, we will feel the borders of our world.

However, first we need a space that we put everything into. ODE organizes the colliding geometry in spaces. We can create as many spaces as we want and we can even add spaces into spaces. This is useful if we want to organize objects in a group which should not allow intercollisions, but that should collide with other objects in our world.

For example, we often need two spaces: A static one and a dynamic one:

globalspace = dspacehash.new()
staticspace = dspacehash.new(globalspace) 
staticspace:collidetest(false) -- don't make collision 
 -- tests in this space

Our static geometry will consist of four planes that are inserted in the global space, describing four walls:

walls = {
  dgeomplane.new(1,0,0,10, staticspace),
  dgeomplane.new(-1,0,0,10, staticspace),
  dgeomplane.new(0,1,0,10, staticspace),
  dgeomplane.new(0,-1,0,10, staticspace),
}

we need now to associate a sphere geometry with our body:

actor.geom = dgeomsphere.new(globalspace)
actor.geom:body(actor.body)

And now, we also need to make a collisiontest, each time when we make a quickstep:

function think ()
  local left = Keyboard.isKeyDown("LEFT")
  local right = Keyboard.isKeyDown("RIGHT")
  local up = Keyboard.isKeyDown("UP")
  local down = Keyboard.isKeyDown("DOWN")
  local fx = (left and 1 or 0) - (right and 1 or 0)
  local fy = (up and 1 or 0) - (down and 1 or 0)
  -- fx,fy: movement directions

  actor.body:addforce(-fx*.01,fy*.01,0)
  dworld.collidetest(globalspace)
  dworld.makecontacts()
  dworld.quickstep(1)
end

note that I reversed the force in x (-fx*.01) and I moved the camera to (6,-15,42) to have a better view on the scene.

We can move now the ball around the scene and it will automaticly stop at the walls - it even rotates when it bounces of the wall due to the default friction!

Step 2: Other bodies

Let's add other bodies and see how they will interact. Let's write a function that allows us to add new bodies in a simple way:

actors = {}
function addbox (x,y,z, w,h,d)
  local actor = actornode.new("box",x,y,z)
  table.insert(actors,actor)
  actor.body = dbody.new()
  actor.geom = dgeombox.new(w,h,d,globalspace)
  actor.l3d = l3dprimitive.newbox("box",w,h,d)
  actor.geom:body(actor.body)
  actor:link(actor.body)
  actor.l3d:linkinterface(actor)
  actor.l3d:rfLitSun(true)
  actor.l3d:color(1,0,0,1)
  actor.body:massbox(1,w,h,d)
end

addbox(4,2,0, 3,2,.5)
addbox(-4,-2,0, 1,2,.5)

We can collide now with the other objects and push them around. Everything acts quite realistic.

Step 3: Gravity and a visible ground

Let's add a groundobject first. I will now not use an actornode but a scenenode. Scenenodes should be used for static geometry. Scenenodes are using less CPU cycles during a frame, since the frustum culling is done only when required. We also need to add the floor geom:

floor = scenenode.new("floor",0,0,-2)
floor.l3d = l3dprimitive.newquadcentered("floor",20,20,1)
 -- never set a scaling factor to 0 - it will result
 -- in strange behaviour because the normals of the 
 -- object might become useles after the transformation
floor.l3d:linkinterface(floor)
floor.l3d:color(0,.5,0,1)
floor.l3d:rfLitSun(true)


walls = {
  dgeomplane.new(1,0,0,-10, staticspace),
  dgeomplane.new(-1,0,0,-10, staticspace),
  dgeomplane.new(0,1,0,-10, staticspace),
  dgeomplane.new(0,-1,0,-10, staticspace),
  dgeomplane.new(0,0,1,-2, staticspace),--floorplane
}

The last thing that we need now is to switch on gravity. Actually, we just need to set the forcevector:

dworld.gravity(0,0,-.01)

After this, our ball will roll around and collides with the boxes that lie around.