physics contact appears bugged

https://youtu.be/cpGWBSCvEZg

recommend you watch this video at 0.25 playback speed

i setup a color change algorithm in the collision function, and i change colors based on contact.state, but the results seem to not be correct, the boxes should be green when contact.state is not ENDED and then back to blue/red when ENDED

function Body:collide(contact)
  if self.body == contact.bodyA or self.body == contact.bodyB then
    if contact.state == BEGAN or contact.state == CHANGED then
      local col = Colors.green
      self.demoMesh.mesh.shader.modColor = vec3(col.r/255, col.g/255, col.b/255)
    elseif contact.state == ENDED then
      local col = Colors[self.color]
      self.demoMesh.mesh.shader.modColor = vec3(col.r/255, col.g/255, col.b/255)
    end
  end
end

Thanks, I’ve passed this report onto @John and he says it does look incorrect

@Simeon @John

i think this bug is on my side because i’m not keeping track of more than 1 collision, so if more than one occurs and just one of them ends, the body goes to the “off” color even if it’s still touching another body…

BUT i noticed that CHANGED is never true so that is another bug

also i noticed using physics.continuous creates a memory leak

you can test it out using this altered Simple Physics example, touch anywhere to create 100 bodies, then repeatedly press the MOVE button, you will see the memory monitor keeps increasing by small increments and never goes back down to the “starting” memory


-- Simple Physics

-- Use this function to perform your initial setup
function setup()
    print("Hello Physics!")
    
    print("Touch the screen to make boxes")
    
  parameter.action('clear', clearBodies)
  parameter.action('move', moveBodies)
    -- Table to store our physics bodies
  walls = {}
    bodies = {}
 
  
  physics.continuous =  true
    
    -- Create some static boxes (not effected by gravity or collisions)
    local left = makeBox(WIDTH/4, HEIGHT/2, WIDTH/3, 15, -30)
    left.type = STATIC
    
    local right = makeBox(WIDTH - WIDTH/4, HEIGHT/2, WIDTH/3, 15, 30)
    right.type = STATIC
    
    local floor = makeBox(WIDTH/2, 10, WIDTH, 20, 0)
    floor.type = STATIC
    
    table.insert(walls, left)
    table.insert(walls, right)
    table.insert(walls, floor)
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color
    background(40, 40, 50)
    
  for k ,body in pairs(walls) do
        drawBody(body)
    end
    -- Draw all our physics bodies
 
    for k,body in pairs(bodies) do
        drawBody(body)
    end
 
  monitor(true)
  
end

function touched(touch)
    -- When you touch the screen, create a random box
    if touch.state == BEGAN then
      for i=1, 100 do
        table.insert(bodies, makeBox(touch.x, touch.y, math.random(5, 50), math.random(5, 50), 0))
      end
    end
end

-- Helper function to create a box using a polygon body
function makeBox(x,y,w,h,r)
    -- Points are defined in counter-clockwise order
    local body = physics.body(POLYGON,vec2(-w/2, h/2),
    vec2(-w/2, -h/2), vec2(w/2, -h/2), vec2(w/2, h/2))
    
    -- Set the body's transform (position, angle)
    body.x = x
    body.y = y
    body.angle = r
    body.bullet = true
    --body.sensor = true
    return body
end

function moveBodies()
  for i = 1, #bodies do
      local demoBody = bodies[i]
      demoBody.linearVelocity = vec2(0,0)
      demoBody.angularVelocity = 0
      demoBody.angle = 0
      local x, y = 600, 600
      demoBody.x = math.random(x - 100, x + 100)
      demoBody.y = math.random(y - 100, y + 100)
      demoBody.type = DYNAMIC
      demoBody.sleepingAllowed = false
    end
end

function clearBodies()
  for i=1, #bodies do
    bodies[i]:destroy()
  end
  bodies = {}
end

-- Helper function to draw a physics body
function drawBody(body)
    -- Push style and transform matrix so we can restore them after
    pushStyle()
    pushMatrix()
    
    strokeWidth(5)
    stroke(148, 224, 135, 255)
    translate(body.x, body.y)
    rotate(body.angle)
    
    -- Draw body based on shape type
    if body.shapeType == POLYGON then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CHAIN or body.shapeType == EDGE then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[j+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CIRCLE then
        strokeWidth(3.0)
        line(0,0,body.radius-3,0)
        ellipse(0,0,body.radius*2)
    end
    
    -- Restore style and transform
    popMatrix()
    popStyle()
end

-- DEV Monitor
local monitorQueue = {}
function queueMonitor(d, v)
  monitorQueue[#monitorQueue + 1] = { desc=d, value=v }
end
function monitor(doCG)
  queueMonitor('framerate: %.3fms', 1000 * DeltaTime) -- realDT)
  queueMonitor('frequency: %ifps', math.floor(1 / DeltaTime)) -- realDT))
  queueMonitor('memory: %.0fkb', collectgarbage('count'))
  zLevel(10)
  fill(30, 30, 30, 150)
  rect(WIDTH/2 - 100, 70, 200, 80)
  fill(200, 200, 200, 255)
  local fullDesc = ''
  local values = {}
  for i=1,#monitorQueue do
    local desc, value = monitorQueue[i].desc, monitorQueue[i].value 
    fullDesc = fullDesc .. desc .. ' \
'
    values[#values + 1] = value
  end
  text(string.format(
    fullDesc,
    table.unpack(values)
  ), WIDTH / 2, 100)
  if doCG then
    collectgarbage()
  end
  monitorQueue = {}
end



@Simeon i have also noticed that there’s a bug with collision in that sometimes the ENDED event comes before the STARTED event

it’s caused me some headache trying to account for it, it happens often when there’s a lot of bodies (100+) OR when a collision is really fast (hit and run)

p.s. turn down your volume or you will get a preview of the music i listen to while coding

Collision Event order bug, fast https://youtu.be/QE_C7Nb4csQ

Codea collision bug, many https://youtu.be/MhxieuEdaDY


if self.body == contact.bodyA or self.body == contact.bodyB then
    if contact.state == BEGAN then
      if self.collisions[contact.id] == 'ended before started' then
        --print('ended before started')
      else
        self.collisions[contact.id] = 'started'
        self.numCollisions = self.numCollisions + 1 
      end
      
      if self.numCollisions > 0 then
        print(self.id, 'col began', self.numCollisions)
        local col = Colors.green
        if self.demoMesh.shader == 'colorSh' then
          self.demoMesh.mesh.shader.modColor = vec3L(col.r/255, col.g/255, col.b/255)
        elseif self.demoMesh.shader == 'fluidCirclesSh' then 
          self.demoMesh.mesh.shader.backgroundColor = vec3L(col.r/255, col.g/255, col.b/255)
        end
      end
    end
    if contact.state == ENDED then
      if self.collisions[contact.id] == 'started' then
        self.collisions[contact.id] = nil
        self.numCollisions = self.numCollisions - 1
      else
        print('ended before started')
        self.collisions[contact.id] = 'ended before started'
      end
  
      if self.numCollisions == 0 then
        print(self.id, 'col ended', self.numCollisions)
        local col = Colors[self.color]
        if self.demoMesh.shader == 'colorSh' then
          self.demoMesh.mesh.shader.modColor = vec3L(col.r/255, col.g/255, col.b/255)
        elseif self.demoMesh.shader == 'fluidCirclesSh' then 
          self.demoMesh.mesh.shader.backgroundColor = vec3L(col.r/255, col.g/255, col.b/255)
        end
      end
    end
  end