Handling cascading collisions


Today I finally managed to wrap my head around a program I'd been struggling with for a few weeks: moving more than one block in response to collisions.

-- each rect is of the form:
--  pos={x=..., y=...} for the center
--  hs={x=..., y=...} for the half-width and half-height
Rects = {}
Move = nil
function car.draw()
  for _,r in ipairs(Rects) do
    color(r.r, r.g, r.b)
    rect('fill', r.pos.x-r.hs.x,r.pos.y-r.hs.y, r.hs.x*2,r.hs.y*2)
  end
end
function car.mousepressed(x,y, button)
  local curr = within_any(x,y)
  if curr then
    Move = {target=curr, dx=x-curr.pos.x, dy=y-curr.pos.y}
  else
    new()
  end
end
function car.mousereleased(x,y, done)
  Move = nil
end
function car.update(dt)
  if Move == nil then return end
  local x,y = love.mouse.getPosition()
  Move.target.pos.x = x-Move.dx
  Move.target.pos.y = y-Move.dy
  move_others(Move.target)
end
-- move any colliding Rects to make room for `a`
function move_others(a)
  for _,r in ipairs(Rects) do
    if r ~= a then
      local msv = collide(a, r)
      if msv then
        r.pos.x = r.pos.x - msv.x
        r.pos.y = r.pos.y - msv.y
        move_others(r)
      end end end end
-- returns the _minimum separation vector_ if there's a collision
function collide(a, b)
  local delta = {x=a.pos.x-b.pos.x, y=a.pos.y-b.pos.y}
  local abs_delta = {x=abs(a.pos.x-b.pos.x), y=abs(a.pos.y-b.pos.y)}
  local size = {x=a.hs.x+b.hs.x, y=a.hs.y+b.hs.y}
  local abs_amount = {x=size.x-abs_delta.x, y=size.y-abs_delta.y}
  if abs_amount.x > 0 and abs_amount.y > 0 then
    if abs_amount.x <= abs_amount.y then
      return {x=abs_amount.x*sign(delta.x), y=0}
    else
      return {x=0, y=abs_amount.y*sign(delta.y)}
    end
  end
end
function within_any(x,y)
  for _,r in ipairs(Rects) do
    if within(r, x,y) then
      return r
    end end end
function within(a, x,y)
  return x > a.pos.x-a.hs.x and x < a.pos.x+a.hs.x
    and y > a.pos.y-a.hs.y and y < a.pos.y+a.hs.y
end
function new()
  while true do
    local r = randrect()
    if not overlap_any(r) then
      table.insert(Rects, r)
      break
    end end end
function randrect()
  local w = rand(15, 60)
  local h = rand(45, 150)
  local x = rand(Safe_width-w-5)
  local y = rand(Safe_height-h-5)
  return {
    pos={x=x, y=y},
    hs={x=w, y=h},
    r=rand(), g=rand(), b=rand()}
end
function overlap_any(a)
  for _,r in ipairs(Rects) do
    if collide(a, r) then
      return true
    end end end
function sign(v)
  if v < 0 then return -1 end
  if v > 0 then return 1 end
  return 0
end

If you try pasting this program into Lua Carousel, remember to first run the abbreviations on one of the example screens. Or if you've deleted that screen, here are the abbreviations this program uses:

g = love.graphics
rect = g.rectangle
color = g.setColor
abs, rand = math.abs, math.random

Get Lua Carousel

Leave a comment

Log in with itch.io to leave a comment.