Body:applyForce

I’m getting some really weird results when calling body:applyForce each frame.

I’m currently trying to work through Chapter 6 of Nature of Code, and I know it’s not practical, but I’m calling body:applyForce on every touch event, just to be as close to the Processing examples as possible.

At the moment my main.lua file is this:

require "physics" physics.start() physics.setGravity(0,0) require "vehicle" local v=vehicle.create() Runtime:addEventListener("touch",function(event) if event.phase=="moved" or event.phase=="began" then v:setTarget(event.x,event.y) end end)

and vehicle.lua looks like this:

local M={} vehicle=M local display=display local math=math require "physics" local physics=physics setfenv(1,M) local maxSpeed=10 local maxForce=10 function create() local img=display.newImage("img/car.png") physics.addBody(img) function img:steer() if not self.targetX then return end local dx,dy=self.targetX-self.x,self.targetY-self.y local d2=dx\*dx+dy\*dy local invMag=maxSpeed/d2^0.5 dx=dx\*invMag dy=dy\*invMag local vx,vy=self:getLinearVelocity() local sx,sy=dx-vx,dy-vy local force2=sx\*sx+sy\*sy if force2\>maxForce\*maxForce and force2\>0 then local r=maxForce/(force2^0.5) sx=sx\*r sy=sy\*r end -- self:setLinearVelocity(vx+sx,vy+sy) self:applyForce(sx,sy,self.x,self.y) end local target function img:setTarget(x,y) self.targetX=x self.targetY=y if target then target:removeSelf() end target=display.newCircle(x,y,20) target:setFillColor(255,0,0) self:steer() end function img:update() self:steer() end return img end return M

When I run it using applyForce calls in vehicle:steer the thing quickly gets juddery and flies of the screen (slowly) if I replace it with the simple call to setLinearVelocity the thing works fine. Am I doing something wrong, or is this a bug with the the applyForce call?

@tap32,

Generally, it is best to apply force every frame and adjust the amount of force elsewhere.

Modified code follows (look for EFM markers):

function create()     local img=display.newImage("img/car.png")     physics.addBody(img, { density = 1 } ) -- EFM             --EFM BEGIN     img.sx = 0     img.sy = 0     img.enterFrame = function( self, event )         self:applyForce( self.sx, self.sy, self.x, self.y )     end Runtime:addEventListener( "enterFrame", img )     --EFM END     function img:steer()         if not self.targetX then             return         end         local dx,dy=self.targetX-self.x,self.targetY-self.y                  local d2=dx\*dx+dy\*dy         local invMag=maxSpeed/d2^0.5         dx=dx\*invMag         dy=dy\*invMag         local vx,vy=self:getLinearVelocity()         local sx,sy=dx-vx,dy-vy         local force2=sx\*sx+sy\*sy         if force2\>maxForce\*maxForce and force2\>0 then             local r=maxForce/(force2^0.5)             sx=sx\*r             sy=sy\*r         end         -- self:setLinearVelocity(vx+sx,vy+sy)         --EFM self:applyForce(sx,sy,self.x,self.y)         self.sx = sx         self.sy = sy     end     local target     function img:setTarget(x,y)         self.targetX=x         self.targetY=y         if target then             target:removeSelf()         end         target=display.newCircle(x,y,20)         target:setFillColor(255,0,0)         self:steer()     end     function img:update()         self:steer()     end     return img end return M  

Note: The changes above only address the how of applying force every frame, not the other problems you mentioned.  This may help though.

Note 2: I also modified the body create call to add density = 1.

Hi again, the more I look at your code, the more I think you will actually ALSO want to move the force calculations over to the enterframe callback too, otherwise, you’ll quickly over-accelerate.

Here is another mod to your code:

local M={} vehicle=M local display=display local math=math require "physics" local physics=physics setfenv(1,M) local maxSpeed=10 local maxForce=10 function create()     local img=display.newImage("img/car.png")     physics.addBody(img, { density = 1 } ) --EFM     function img:steer( event )         if not self.targetX then             return         end         local dx,dy=self.targetX-self.x,self.targetY-self.y                  local d2=dx\*dx+dy\*dy         local invMag=maxSpeed/d2^0.5         dx=dx\*invMag         dy=dy\*invMag         local vx,vy=self:getLinearVelocity()         local sx,sy=dx-vx,dy-vy         local force2=sx\*sx+sy\*sy         if force2\>maxForce\*maxForce and force2\>0 then             local r=maxForce/(force2^0.5)             sx=sx\*r             sy=sy\*r         end         -- self:setLinearVelocity(vx+sx,vy+sy)         self:applyForce(sx,sy,self.x,self.y)     end     --EFM begin     img.enterFrame = img.steer     Runtime:addEventListener( "enterFrame", img)     --EFM end     local target     function img:setTarget(x,y)         self.targetX=x         self.targetY=y         if target then             target:removeSelf()         end         target=display.newCircle(x,y,20)         target:setFillColor(255,0,0)         --EFM self:steer()     end --EFM    function img:update() --EFM        self:steer() --EFM   end     return img end return M  

I’m not sure if you’re interested, but I wouldn’t mind seeing the whole project and poking at it.  If so, I can be reached here:  rgemail2.png

A zip or rar would be cool.

Hi thanks for that! 

Work beautifully! It looks like that adding the density of 1 really made the difference. Does Corona not supply a sensible default?

Regards sharing the project, at the moment you’ve seen 100% of it. What I’ll do though is stick it up on GitHub as it grows. I’ve decided to just work through that Nature of Code book I linked to (starting at Chapter 6) and write it in Lua. I want to get a bit more experience writing these sorts of agents. After that I’m going to have a go at implementing Jump Point Search! (one day I may tie everything together for a game!)

@tap32,

If I recall, Corona used to have density defaulted to 1.0, but then along the way I noticed that value stopped getting set.  I’m not sure now as I always set it by default to be sure I get what I’m looking for.

Note: Your relative mass will be the width x height x density of the object, so don’t be surprised if you get different responses from objects that take more or less screen space.

@tap32,

Generally, it is best to apply force every frame and adjust the amount of force elsewhere.

Modified code follows (look for EFM markers):

function create()     local img=display.newImage("img/car.png")     physics.addBody(img, { density = 1 } ) -- EFM             --EFM BEGIN     img.sx = 0     img.sy = 0     img.enterFrame = function( self, event )         self:applyForce( self.sx, self.sy, self.x, self.y )     end Runtime:addEventListener( "enterFrame", img )     --EFM END     function img:steer()         if not self.targetX then             return         end         local dx,dy=self.targetX-self.x,self.targetY-self.y                  local d2=dx\*dx+dy\*dy         local invMag=maxSpeed/d2^0.5         dx=dx\*invMag         dy=dy\*invMag         local vx,vy=self:getLinearVelocity()         local sx,sy=dx-vx,dy-vy         local force2=sx\*sx+sy\*sy         if force2\>maxForce\*maxForce and force2\>0 then             local r=maxForce/(force2^0.5)             sx=sx\*r             sy=sy\*r         end         -- self:setLinearVelocity(vx+sx,vy+sy)         --EFM self:applyForce(sx,sy,self.x,self.y)         self.sx = sx         self.sy = sy     end     local target     function img:setTarget(x,y)         self.targetX=x         self.targetY=y         if target then             target:removeSelf()         end         target=display.newCircle(x,y,20)         target:setFillColor(255,0,0)         self:steer()     end     function img:update()         self:steer()     end     return img end return M  

Note: The changes above only address the how of applying force every frame, not the other problems you mentioned.  This may help though.

Note 2: I also modified the body create call to add density = 1.

Hi again, the more I look at your code, the more I think you will actually ALSO want to move the force calculations over to the enterframe callback too, otherwise, you’ll quickly over-accelerate.

Here is another mod to your code:

local M={} vehicle=M local display=display local math=math require "physics" local physics=physics setfenv(1,M) local maxSpeed=10 local maxForce=10 function create()     local img=display.newImage("img/car.png")     physics.addBody(img, { density = 1 } ) --EFM     function img:steer( event )         if not self.targetX then             return         end         local dx,dy=self.targetX-self.x,self.targetY-self.y                  local d2=dx\*dx+dy\*dy         local invMag=maxSpeed/d2^0.5         dx=dx\*invMag         dy=dy\*invMag         local vx,vy=self:getLinearVelocity()         local sx,sy=dx-vx,dy-vy         local force2=sx\*sx+sy\*sy         if force2\>maxForce\*maxForce and force2\>0 then             local r=maxForce/(force2^0.5)             sx=sx\*r             sy=sy\*r         end         -- self:setLinearVelocity(vx+sx,vy+sy)         self:applyForce(sx,sy,self.x,self.y)     end     --EFM begin     img.enterFrame = img.steer     Runtime:addEventListener( "enterFrame", img)     --EFM end     local target     function img:setTarget(x,y)         self.targetX=x         self.targetY=y         if target then             target:removeSelf()         end         target=display.newCircle(x,y,20)         target:setFillColor(255,0,0)         --EFM self:steer()     end --EFM    function img:update() --EFM        self:steer() --EFM   end     return img end return M  

I’m not sure if you’re interested, but I wouldn’t mind seeing the whole project and poking at it.  If so, I can be reached here:  rgemail2.png

A zip or rar would be cool.

Hi thanks for that! 

Work beautifully! It looks like that adding the density of 1 really made the difference. Does Corona not supply a sensible default?

Regards sharing the project, at the moment you’ve seen 100% of it. What I’ll do though is stick it up on GitHub as it grows. I’ve decided to just work through that Nature of Code book I linked to (starting at Chapter 6) and write it in Lua. I want to get a bit more experience writing these sorts of agents. After that I’m going to have a go at implementing Jump Point Search! (one day I may tie everything together for a game!)

@tap32,

If I recall, Corona used to have density defaulted to 1.0, but then along the way I noticed that value stopped getting set.  I’m not sure now as I always set it by default to be sure I get what I’m looking for.

Note: Your relative mass will be the width x height x density of the object, so don’t be surprised if you get different responses from objects that take more or less screen space.