physics object passing through edges

I’ve noticed that some boucing balls pass through Edge objects, even when ball.bullet= true. Is this expected? Looks like a physics bug.

@Jmv38 Do you have physics.continuous=true in setup().

run this and wait after 30s some balls will go through the edges, i made a sound and a print to show the event.


--# EventMngr
-- ############## START of EVENT MANAGER ##################
-- @tnlogy & @JMV38 & @Briarfox
-- example of usage:
--    EventMngr:extend(evMngr)           -- extend an existing table with event manager funcs
--    evMngr:on("touch",func)            -- register func() to fire on "touch" event
--    evMngr:on("touch", obj.func, obj)  -- register obj:func() to fire on "touch" event
--    evMngr:trigger("touch",10,50)      -- fires func(10,50) and obj:func(10,50)
--    evMngr:off("touch", func)          -- unregister func()
--    evMngr:off("touch", obj.func, obj) -- unregister obj:func()
--    evMngr:off("touch")                -- unregister all "touch" listeners
--    evMngr:off(obj.func)               -- unregister all listeners with obj.func
--    evMngr:off(obj)                    -- unregister events with obj listening
--    "all" captures all events and passes the event name as the first param:
--    evMngr:on("all", func) 

EventMngr = {}

local fifo = true -- first in (to register) first out (to be triggered)
function EventMngr:on(eventName, fn, obj)

    if not self.events then self.events = {} end -- init event table if does not exist
--    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    
    local new = true -- confirm it is a new request
    for i,fa in ipairs(self.events[eventName]) do
        if fa.func == fn and fa.obj == obj then new = false end
    end
    
    local p -- insertion point in the table
    if new then 
        if fifo then p = 1 else p = #self.events[eventName] +1 ; fifo=true end
        local listener = {func = fn, obj = obj }
        table.insert(self.events[eventName], p, listener) 
    end

    return self
end

function EventMngr:executeNextCallBeforeOthers()
    fifo = false
end

function EventMngr:off(nameOrFnOrObj, fn, obj)
    local name
    local fn,obj = fn,obj -- manage the case when they are nil
    local firstType = type(nameOrFnOrObj)
    local request
    if firstType == "string" or firstType == "number" then 
        name = nameOrFnOrObj
        if name == "all" then request = "remove all events"
        elseif fn == nil then request = "remove all instances of this event"
        else request = "remove this event" end
    elseif firstType == "function" then 
        fn = nameOrFnOrObj
        request = "remove all events with this function"
    else 
        obj = nameOrFnOrObj
        request = "remove all events with this object" 
    end

    if request == "remove all instances of this event" then
        self.events[name] = nil
    elseif request == "remove all events" then
        self.events = {}
    else
        local evs = self.events            -- go through all events ...
        if name then evs = {evs[name]} end -- ... or through 1 event only
        for eventName,fns in pairs(evs) do
            local n = #fns
            for i=0,n-1 do
                local j = n-i  -- go backward because of remove, ipairs not suitable
                local f = fns[j] 
                local match
                if request == "remove this event" 
                then match=(f.func==fn and f.obj==obj)
                elseif request == "remove all events with this function" 
                then match=(f.func==fn)
                elseif request == "remove all events with this object" 
                then match=(f.obj==obj)
                end
                if match then 
                    table.remove(fns,j) 
                end
            end
        end
    end
    return self
end

function EventMngr:trigger(name, ...)
    self.lastTrigger = name
    local evs = (self.events and self.events[name]) or {}
    for i = #evs,1,-1 do
        local fa = evs[i]
        local func,obj = fa.func, fa.obj
        if obj then func(obj,...) 
        else func(...) end
    end
    --trigger all
    local evs = (self.events and self.events["all"]) or {}
    for i,fa in ipairs(evs) do 
        local func,obj = fa.func, fa.obj
        if obj then func(obj,name,...) 
        else func(name,...) end
    end
end
        
-- to transform a table into an event manager
function EventMngr:extend(target)
    for k, v in pairs(self) do
        if type(v) == "function" and v ~= EventMngr.extend
        then target[k] = v 
        end
    end
    return target
end


-- ############## END of EVENT MANAGER ##################




--# World
World = class()
EventMngr:extend(World)

function World:init()
end

function World:draw()
    self:trigger("draw")
end

function World:touched(touch)
    self:trigger("touched", touch)
end

--# Edge
Edge = class()
EventMngr:extend(Edge)

function Edge:init(data)
    if data.x then
        local x = data.x
        self.pos0 = vec2(x,0)
        self.pos1 = vec2(x,HEIGHT)
    elseif data.y then
        local y = data.y
        self.pos0 = vec2(0, y)
        self.pos1 = vec2(WIDTH, y)
    else
        error("please define x or y")
    end
    self.body = physics.body(EDGE, self.pos0, self.pos1)
    self.body.info = self
    -- events
    World:on("draw",self.draw,self)
    World:executeNextCallBeforeOthers()
    World:on("touched",self.touched,self)
end

function Edge:draw()
    fill(223, 223, 223, 255)
    line(self.pos0.x, self.pos0.y, self.pos1.x, self.pos1.y)
end

function Edge:touched(touch)

end

--# Ball
Ball = class()

function Ball:init(x,y,r)
    self.x = x
    self.y = y
    self.r = r
    self:updateColor()
    self.body = physics.body(CIRCLE, self.r)
    self.body.x = self.x
    self.body.y = self.y
    self.body.sleepingAllowed = false
    self.body.gravityScale = 1
    self.body.linearVelocity = vec2(400,200)
    self.body.linearDamping = 0
    self.body.angularDamping = 0
    self.body.friction = 0
    self.body.restitution = 1
    self.body.info = self
    self.body.bullet = true
    -- events
    World:on("draw",self.draw,self)
    self.outside = false
end

function Ball:delete()
    self.body:destroy()
    World:off(self)
end

function Ball:draw()
    local pos = self.body.position
    self.x, self.y = pos.x, pos.y
    fill(self.color)
    ellipse(self.x,self.y,self.r*2)
    if self.outside==false and (self.x<0 or self.y<0 or self.x>WIDTH or self.y>HEIGHT) then
        self.outside=true
        print("out!")
        sound("Game Sounds One:Zapper 1")
    end
end

local rand = math.random
function Ball:updateColor()
    -- lets change color
    local r,g = rand(255),rand(255)
    local b = 255 - g
    self.color = color(r,g,b)
end
local abs = math.abs
function Ball:touched(t)
    if abs(self.x-t.x)<self.r and abs(self.x-t.x)<self.r then
        if t.state == BEGAN then 
        end
    end
end

--# MotherBall
MotherBall = class(Ball)

function MotherBall:init(x,y,r)
    Ball.init(self, x,y,r)
    self.body.gravityScale = 0
    World:on("draw",self.drawText,self)
end
function MotherBall:drawText()
    fill(0)
    fontSize(30)
    local t = World.events["delete"]
    if t then text( tostring(#t), self.x,self.y) end
end
function MotherBall:babyBall()
    local baby = Ball(self.x,self.y,self.r/3)
    World:on("delete",baby.delete,baby)
end
function MotherBall:collide(c)
    -- lets change color
    self:updateColor()
    -- if i touch a wall, make a baby ball
    if c.bodyA.info:is_a(Edge) or c.bodyB.info:is_a(Edge) then self:babyBall() end
end


--# Main
-- eventsExample

-- Use this function to perform your initial setup
function setup()
    e1 = Edge({x=0})
    e2 = Edge({x=WIDTH})
    e3 = Edge({y=0})
    e4 = Edge({y=HEIGHT})
    b = MotherBall(WIDTH/2, HEIGHT/2, 50)
    parameter.action("delete",function() World:trigger("delete") collectgarbage() end )
    
end

-- This function gets called once every frame
function draw()
    background(40, 40, 50)
    strokeWidth(1.5)
    noSmooth() 
    World:draw()
end

function touched(t)
    World:touched(t)
end

function collide(c)
    if c.state == BEGAN then 
        fA = c.bodyA.info.collide
        if fA then fA(c.bodyA.info, c) end
        fB = c.bodyB.info.collide
        if fB then fB(c.bodyB.info, c) end
    end
end

@Jmv38 My wife just informed me we’re going to the store. Will look at this when I get back.

here is a new version: i’ve put a ghost red ball where it disappears. There doesnt seem to be a special location.


--# EventMngr
-- ############## START of EVENT MANAGER ##################
-- @tnlogy & @JMV38 & @Briarfox
-- example of usage:
--    EventMngr:extend(evMngr)           -- extend an existing table with event manager funcs
--    evMngr:on("touch",func)            -- register func() to fire on "touch" event
--    evMngr:on("touch", obj.func, obj)  -- register obj:func() to fire on "touch" event
--    evMngr:trigger("touch",10,50)      -- fires func(10,50) and obj:func(10,50)
--    evMngr:off("touch", func)          -- unregister func()
--    evMngr:off("touch", obj.func, obj) -- unregister obj:func()
--    evMngr:off("touch")                -- unregister all "touch" listeners
--    evMngr:off(obj.func)               -- unregister all listeners with obj.func
--    evMngr:off(obj)                    -- unregister events with obj listening
--    "all" captures all events and passes the event name as the first param:
--    evMngr:on("all", func) 

EventMngr = {}

local fifo = true -- first in (to register) first out (to be triggered)
function EventMngr:on(eventName, fn, obj)

    if not self.events then self.events = {} end -- init event table if does not exist
--    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    
    local new = true -- confirm it is a new request
    for i,fa in ipairs(self.events[eventName]) do
        if fa.func == fn and fa.obj == obj then new = false end
    end
    
    local p -- insertion point in the table
    if new then 
        if fifo then p = 1 else p = #self.events[eventName] +1 ; fifo=true end
        local listener = {func = fn, obj = obj }
        table.insert(self.events[eventName], p, listener) 
    end

    return self
end

function EventMngr:executeNextCallBeforeOthers()
    fifo = false
end

function EventMngr:off(nameOrFnOrObj, fn, obj)
    local name
    local fn,obj = fn,obj -- manage the case when they are nil
    local firstType = type(nameOrFnOrObj)
    local request
    if firstType == "string" or firstType == "number" then 
        name = nameOrFnOrObj
        if name == "all" then request = "remove all events"
        elseif fn == nil then request = "remove all instances of this event"
        else request = "remove this event" end
    elseif firstType == "function" then 
        fn = nameOrFnOrObj
        request = "remove all events with this function"
    else 
        obj = nameOrFnOrObj
        request = "remove all events with this object" 
    end

    if request == "remove all instances of this event" then
        self.events[name] = nil
    elseif request == "remove all events" then
        self.events = {}
    else
        local evs = self.events            -- go through all events ...
        if name then evs = {evs[name]} end -- ... or through 1 event only
        for eventName,fns in pairs(evs) do
            local n = #fns
            for i=0,n-1 do
                local j = n-i  -- go backward because of remove, ipairs not suitable
                local f = fns[j] 
                local match
                if request == "remove this event" 
                then match=(f.func==fn and f.obj==obj)
                elseif request == "remove all events with this function" 
                then match=(f.func==fn)
                elseif request == "remove all events with this object" 
                then match=(f.obj==obj)
                end
                if match then 
                    table.remove(fns,j) 
                end
            end
        end
    end
    return self
end

function EventMngr:trigger(name, ...)
    self.lastTrigger = name
    local evs = (self.events and self.events[name]) or {}
    for i = #evs,1,-1 do
        local fa = evs[i]
        local func,obj = fa.func, fa.obj
        if obj then func(obj,...) 
        else func(...) end
    end
    --trigger all
    local evs = (self.events and self.events["all"]) or {}
    for i,fa in ipairs(evs) do 
        local func,obj = fa.func, fa.obj
        if obj then func(obj,name,...) 
        else func(name,...) end
    end
end
        
-- to transform a table into an event manager
function EventMngr:extend(target)
    for k, v in pairs(self) do
        if type(v) == "function" and v ~= EventMngr.extend
        then target[k] = v 
        end
    end
    return target
end


-- ############## END of EVENT MANAGER ##################




--# World
World = class()
EventMngr:extend(World)

function World:init()
end

function World:draw()
    self:trigger("draw")
end

function World:touched(touch)
    self:trigger("touched", touch)
end

--# Edge
Edge = class()
EventMngr:extend(Edge)

function Edge:init(data)
    if data.x then
        local x = data.x
        self.pos0 = vec2(x,0)
        self.pos1 = vec2(x,HEIGHT)
    elseif data.y then
        local y = data.y
        self.pos0 = vec2(0, y)
        self.pos1 = vec2(WIDTH, y)
    else
        error("please define x or y")
    end
    self.body = physics.body(EDGE, self.pos0, self.pos1)
    self.body.info = self
    -- events
    World:on("draw",self.draw,self)
    World:executeNextCallBeforeOthers()
    World:on("touched",self.touched,self)
end

function Edge:draw()
    fill(223, 223, 223, 255)
    line(self.pos0.x, self.pos0.y, self.pos1.x, self.pos1.y)
end

function Edge:touched(touch)

end

--# Ball
Ball = class()

function Ball:init(x,y,r)
    self.x = x
    self.y = y
    self.r = r
    self:updateColor()
    self.body = physics.body(CIRCLE, self.r)
    self.body.x = self.x
    self.body.y = self.y
    self.body.sleepingAllowed = false
    self.body.gravityScale = 1
    self.body.linearVelocity = vec2(400,200)
    self.body.linearDamping = 0
    self.body.angularDamping = 0
    self.body.friction = 0
    self.body.restitution = 1
    self.body.info = self
    self.body.bullet = true
    -- events
    World:on("draw",self.draw,self)
    self.outside = false
end

function Ball:delete()
    self.body:destroy()
    World:off(self)
end

function Ball:draw()
    local pos = self.body.position
    local x0,y0 = self.x, self.y
    if self.outside==false and (pos.x<0 or pos.y<0 or pos.x>WIDTH or pos.y>HEIGHT) then
        self.outside=true
        print("out!")
        sound("Game Sounds One:Zapper 1")
        self.color = color(255, 0, 0, 255)
    end
    if self.outside==false then
        self.x, self.y = pos.x, pos.y
    end
    fill(self.color)
    ellipse(self.x,self.y,self.r*2)

end

local rand = math.random
function Ball:updateColor()
    -- lets change color
    local r,g = rand(255),rand(255)
    local b = 255 - g
    self.color = color(r,g,b)
end
local abs = math.abs
function Ball:touched(t)
    if abs(self.x-t.x)<self.r and abs(self.x-t.x)<self.r then
        if t.state == BEGAN then 
        end
    end
end

--# MotherBall
MotherBall = class(Ball)

function MotherBall:init(x,y,r)
    Ball.init(self, x,y,r)
    self.body.gravityScale = 0
    World:on("draw",self.drawText,self)
end
function MotherBall:drawText()
    fill(0)
    fontSize(30)
    local t = World.events["delete"]
    if t then text( tostring(#t), self.x,self.y) end
end
function MotherBall:babyBall()
    local baby = Ball(self.x,self.y,self.r/3)
    World:on("delete",baby.delete,baby)
end
function MotherBall:collide(c)
    -- lets change color
    self:updateColor()
    -- if i touch a wall, make a baby ball
    if c.bodyA.info:is_a(Edge) or c.bodyB.info:is_a(Edge) then self:babyBall() end
end


--# Main
-- eventsExample

-- Use this function to perform your initial setup
function setup()
    e1 = Edge({x=0})
    e2 = Edge({x=WIDTH})
    e3 = Edge({y=0})
    e4 = Edge({y=HEIGHT})
    b = MotherBall(WIDTH/2, HEIGHT/2, 50)
    parameter.action("delete",function() World:trigger("delete") collectgarbage() end )
    
end

-- This function gets called once every frame
function draw()
    background(40, 40, 50)
    strokeWidth(1.5)
    noSmooth() 
    World:draw()
end

function touched(t)
    World:touched(t)
end

function collide(c)
    if c.state == BEGAN then 
        fA = c.bodyA.info.collide
        if fA then fA(c.bodyA.info, c) end
        fB = c.bodyB.info.collide
        if fB then fB(c.bodyB.info, c) end
    end
end


very strange!
i’ve added physics.contnuous=true, but
1/ some balls still bo through.
2/ they go through further from the wall than before!!! ???

@Jmv38 Gonna have a look at it now see whats happening.

This seems to be okay:


--# EventMngr
-- ############## START of EVENT MANAGER ##################
-- @tnlogy & @JMV38 & @Briarfox
-- example of usage:
--    EventMngr:extend(evMngr)           -- extend an existing table with event manager funcs
--    evMngr:on("touch",func)            -- register func() to fire on "touch" event
--    evMngr:on("touch", obj.func, obj)  -- register obj:func() to fire on "touch" event
--    evMngr:trigger("touch",10,50)      -- fires func(10,50) and obj:func(10,50)
--    evMngr:off("touch", func)          -- unregister func()
--    evMngr:off("touch", obj.func, obj) -- unregister obj:func()
--    evMngr:off("touch")                -- unregister all "touch" listeners
--    evMngr:off(obj.func)               -- unregister all listeners with obj.func
--    evMngr:off(obj)                    -- unregister events with obj listening
--    "all" captures all events and passes the event name as the first param:
--    evMngr:on("all", func) 

EventMngr = {}

local fifo = true -- first in (to register) first out (to be triggered)
function EventMngr:on(eventName, fn, obj)

    if not self.events then self.events = {} end -- init event table if does not exist
--    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name

    local new = true -- confirm it is a new request
    for i,fa in ipairs(self.events[eventName]) do
        if fa.func == fn and fa.obj == obj then new = false end
    end

    local p -- insertion point in the table
    if new then 
        if fifo then p = 1 else p = #self.events[eventName] +1 ; fifo=true end
        local listener = {func = fn, obj = obj }
        table.insert(self.events[eventName], p, listener) 
    end

    return self
end

function EventMngr:executeNextCallBeforeOthers()
    fifo = false
end

function EventMngr:off(nameOrFnOrObj, fn, obj)
    local name
    local fn,obj = fn,obj -- manage the case when they are nil
    local firstType = type(nameOrFnOrObj)
    local request
    if firstType == "string" or firstType == "number" then 
        name = nameOrFnOrObj
        if name == "all" then request = "remove all events"
        elseif fn == nil then request = "remove all instances of this event"
        else request = "remove this event" end
    elseif firstType == "function" then 
        fn = nameOrFnOrObj
        request = "remove all events with this function"
    else 
        obj = nameOrFnOrObj
        request = "remove all events with this object" 
    end

    if request == "remove all instances of this event" then
        self.events[name] = nil
    elseif request == "remove all events" then
        self.events = {}
    else
        local evs = self.events            -- go through all events ...
        if name then evs = {evs[name]} end -- ... or through 1 event only
        for eventName,fns in pairs(evs) do
            local n = #fns
            for i=0,n-1 do
                local j = n-i  -- go backward because of remove, ipairs not suitable
                local f = fns[j] 
                local match
                if request == "remove this event" 
                then match=(f.func==fn and f.obj==obj)
                elseif request == "remove all events with this function" 
                then match=(f.func==fn)
                elseif request == "remove all events with this object" 
                then match=(f.obj==obj)
                end
                if match then 
                    table.remove(fns,j) 
                end
            end
        end
    end
    return self
end

function EventMngr:trigger(name, ...)
    self.lastTrigger = name
    local evs = (self.events and self.events[name]) or {}
    for i = #evs,1,-1 do
        local fa = evs[i]
        local func,obj = fa.func, fa.obj
        if obj then func(obj,...) 
        else func(...) end
    end
    --trigger all
    local evs = (self.events and self.events["all"]) or {}
    for i,fa in ipairs(evs) do 
        local func,obj = fa.func, fa.obj
        if obj then func(obj,name,...) 
        else func(name,...) end
    end
end

-- to transform a table into an event manager
function EventMngr:extend(target)
    for k, v in pairs(self) do
        if type(v) == "function" and v ~= EventMngr.extend
        then target[k] = v 
        end
    end
    return target
end


-- ############## END of EVENT MANAGER ##################





--# World
World = class()
EventMngr:extend(World)

function World:init()
end

function World:draw()
    self:trigger("draw")
end

function World:touched(touch)
    self:trigger("touched", touch)
end


--# Edge
Edge = class()
EventMngr:extend(Edge)

function Edge:init(data)
    if data.x then
        local x = data.x
        self.pos0 = vec2(x,0)
        self.pos1 = vec2(x,HEIGHT)
    elseif data.y then
        local y = data.y
        self.pos0 = vec2(0, y)
        self.pos1 = vec2(WIDTH, y)
    else
        error("please define x or y")
    end
    self.body = physics.body(EDGE, self.pos0, self.pos1)
    self.body.info = self
    -- events
    World:on("draw",self.draw,self)
    World:executeNextCallBeforeOthers()
    World:on("touched",self.touched,self)
end

function Edge:draw()
    fill(223, 223, 223, 255)
    line(self.pos0.x, self.pos0.y, self.pos1.x, self.pos1.y)
end

function Edge:touched(touch)

end


--# Ball
Ball = class()

function Ball:init(x,y,r)
    self.x = x
    self.y = y
    self.r = r
    self:updateColor()
    self.body = physics.body(CIRCLE, self.r)
    self.body.x = self.x
    self.body.y = self.y
    self.body.sleepingAllowed = false
    self.body.gravityScale = 1
    self.body.linearVelocity = vec2(400,200)
    self.body.linearDamping = 0
    self.body.angularDamping = 0
    self.body.friction = 0
    self.body.restitution = 1
    self.body.info = self
    self.body.bullet = true
    -- events
    World:on("draw",self.draw,self)
    self.outside = false
end

function Ball:delete()
    self.body:destroy()
    World:off(self)
end

function Ball:draw()
    local pos = self.body.position
    local x0,y0 = self.x, self.y
    if self.outside==false and (pos.x<0 or pos.y<0 or pos.x>WIDTH or pos.y>HEIGHT) then
        self.outside=true
        print("out!")
        sound("Game Sounds One:Zapper 1")
        self.color = color(255, 0, 0, 255)
    end
    if self.outside==false then
        self.x, self.y = pos.x, pos.y
    end
    fill(self.color)
    ellipse(self.x,self.y,self.r*2)

end

local rand = math.random
function Ball:updateColor()
    -- lets change color
    local r,g = rand(255),rand(255)
    local b = 255 - g
    self.color = color(r,g,b)
end
local abs = math.abs
function Ball:touched(t)
    if abs(self.x-t.x)<self.r and abs(self.x-t.x)<self.r then
        if t.state == BEGAN then 
        end
    end
end


--# MotherBall
MotherBall = class(Ball)

function MotherBall:init(x,y,r)
    Ball.init(self, x,y,r)
    self.body.gravityScale = 0
    World:on("draw",self.drawText,self)
end
function MotherBall:drawText()
    fill(0)
    fontSize(30)
    local t = World.events["delete"]
    if t then text( tostring(#t), self.x,self.y) end
end
function MotherBall:babyBall(norm)
    local baby = Ball(self.x+norm.x*50,self.y+norm.y*50,self.r/3)
    baby.body.linearVelocity = (norm*300)
    World:on("delete",baby.delete,baby)
end
function MotherBall:collide(c)
    -- lets change color
    self:updateColor()
    -- if i touch a wall, make a baby ball
    if c.bodyA.info:is_a(Edge) then print(c.id,c.state) self:babyBall(c.normal) end
end



--# Main
-- eventsExample

-- Use this function to perform your initial setup
function setup()
    e1 = Edge({x=0})
    e2 = Edge({x=WIDTH})
    e3 = Edge({y=0})
    e4 = Edge({y=HEIGHT})
    b = MotherBall(WIDTH/2, HEIGHT/2, 50)
    parameter.action("delete",function() World:trigger("delete") collectgarbage() end )
    physics.iterations(10,10)
    physics.pixelToMeterRatio = 32
    physics.continuous = true
end

-- This function gets called once every frame
function draw()
    background(40, 40, 50)
    strokeWidth(1.5)
    noSmooth() 
    World:draw()
end

function touched(t)
    World:touched(t)
end

function collide(c)
    if c.state == BEGAN then 
        fA = c.bodyA.info.collide
        if fA then fA(c.bodyA.info, c) end
        fB = c.bodyB.info.collide
        if fB then fB(c.bodyB.info, c) end
    end
end



The problem as it seems is if you create a ball inside the mother ball at the centre then the physics engine has to pick a direction to leave, if that direction is towards the wall then the ball will pass right through. It does this because it is ejected from the mother ball in one iteration, meaning it pretty much just sets the balls position to outside the mother ball without a collision check. I’ve added a passing of the collision normal to the baby ball function to set the direction to the normal of a the collision, removing the above problem.

@Luatee I don’t think that’s the whole problem. I added code to assign a number to each ball as its created. I then printed the number of the ball that went out of bounds and the number of the last created ball. The numbers weren’t the same.

@Jmv38 I noticed that the linearVelocity of the balls going out of bounds fast. They were either above 1000 or below -1000. Those speeds aren’t that high, so something else is going on.

@Dave1707 i agree, the balls passing through are fast ones.
@luatee it is not a problem at ball creation (although there may be a problem there too), but long after the ball has been created.

@luatee i’ve tried your code and you are right, there is no bug with it. Is the ball creation the only thing you have changed? Because the desappearing balls are not just created ones… Then it must be a ‘second order’ bug generated by the initial bug of throwing some balls out of the bounds…
Nice finding, anyway. ^:)^

Here’s something I was playing with. 3840 is the max linearVelocity no matter how high the applyForce is set to. None of the balls go through the edge when the screen is tapped. The linearVelocity is a lot higher here than in the above code when a ball goes through an edge. Comment out the line physics.continuous=true just to see what it does. So something else is happening in the above code.


displayMode(FULLSCREEN)
supportedOrientations(LANDSCAPE_ANY)

function setup()
    physics.continuous=true
    max=0
    tab={}
    e=physics.body(EDGE,vec2(WIDTH-100,0),vec2(WIDTH-100,HEIGHT))
    for z=1,70 do
        a=physics.body(CIRCLE,5)
        a.x=z*4
        a.y=z*10
        a.gravityScale=0
        table.insert(tab,a)
    end
end

function draw()
    background(40, 40, 50)
    fill(255)
    stroke(255)
    strokeWidth(2)
    line(WIDTH-100,0,WIDTH-100,HEIGHT)
    for a,b in pairs(tab) do
        ellipse(b.x,b.y,10)
    end
    if tab[1].linearVelocity.x>max then
        max=tab[1].linearVelocity.x
    end
    text("max linear velocity "..max,WIDTH/2,HEIGHT-100)
    text("tap screen to apply force",WIDTH/2,HEIGHT-150)
end

function touched(t)
    if t.state==BEGAN then
        for a,b in pairs(tab) do
            b:applyForce(vec2(8000,0))            
        end
    end
end

@Jmv38 When I watch what’s going on, I see balls going out of bounds even when the large ball is in the middle of the screen an hasn’t created a new ball for awhile.

@Dave1707 right. But with luatee code, no ball EVER goes out of bounds. So this bug is linked in some strange way with throwing balls out of bounds much earlier… Maybe they anihilate with in bounds balls? Like anti-mater?
Maybe we’ve just discovered the ‘Digital Tunnel Effect’? Maybe we get the Nobel prize for that? Or, more likely, the Ig-Nobel prize. :wink:

@Jmv38 I made another change to your code. After 20 balls are created, I don’t create anymore, but balls still go out of bounds after that.

I’ve been up to 270 balls with luatee version. No out of bounds. But Codea freezes…

@Jmv38 That may be the solution, but I’m still curious why balls go out of bounds even when I don’t create any new ones. Their linearVelocity doesn’t seem high enough for them to go through a wall.

@Jmv38 I don’t know why I didn’t think of this sooner, but I did a video recording of your running program and stopped it after I saw an out of bounds ball. I played the video back and used the drag bar to run the video backward and forward so I could see what happened when a ball went out of bounds. It was the result of a collision with a fast ball that just collided with another ball. Maybe I’ll try some more videos.

nice detective work!