[Help] Circle Collision detection and reaction

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
andrew.207
Prole
Posts: 19
Joined: Sat Jun 08, 2013 7:27 am
Location: Melbourne, Australia

[Help] Circle Collision detection and reaction

Post by andrew.207 »

I'm still very new to LUA/Love2D, so let me know if there are any glaring code structure issues. I have come from a heavy OO background, which I believe is quite bad for LUA.

My project is creating a simple circle collision detection and rebound system. It detects the collision fine, and I'm pretty sure my maths is right, I think my logic is very wrong though. My aim is to have the rebound occur close to real life -- taking into account changes in angle as well as speed.

If anyone could help me out with suggestions or links to examples that'd be a great help. Doesn't have to be LUA examples, can be virtually any language -- even just theory.

BTW I looked through hardon collider and it went way over my head.

The code below is the important bit, within object:update(dt). Each shape has the obvious vars (x, y, speeds, size etc) and also an ID to act as a unique identifier for each object to make this stuff easier.

edit: forgot to say I've already read http://www.wildbunny.co.uk/blog/2011/04 ... r-dummies/ and I just don't understand the last little bit about the vector.

Code: Select all

-- Check if any shapes are colliding with each other
  -- Objects are on top of each other if the distance between them is greater than the sum of their radii.
  -- For every shape in the shape table
  for di,dv in ipairs(shapes) do  
    if (self.id ~= dv.id) then -- If we're not colliding with ourself
      -- Distance between the two circles
      H = math.distanceBetween(self.x, self.y, dv.x, dv.y)   
      local A = self.x - dv.x -- Distance of adjacent edge
      -- Angle given when a right angle triangle is created with self and dv
      local angleOfCollision = math.deg(1/(math.cos(A/H))) 
      -- Sum of the radii 
      local sumOfRadii = (self.radius*scale) + (dv.radius*scale) 
      local levelOfPenetration = H - sumOfRadii -- Level of penetration (-ve if collision)
      local forceOfHit = (math.max(self.xSpeed, self.ySpeed) - math.max(dv.xSpeed, dv.ySpeed))

      if levelOfPenetration < 0 and forceOfHit > 0 then
        -- super basic ejection
        if (self.x > dv.x) then
          self.x = self.x - levelOfPenetration
        else
          self.x = self.x + levelOfPenetration
        end
        if (self.y > dv.y) then
          self.y = self.y - levelOfPenetration
        else
          self.y = self.y + levelOfPenetration
        end
      
          -- Apply base speed modifications
          local temp = self.xSpeed
          if (dv.xSpeed > 0) then
            self.xSpeed = self.xSpeed - dv.xSpeed*bounciness
          else
            self.xSpeed = self.xSpeed + dv.xSpeed*bounciness
          end
          if (temp > 0) then
            dv.xSpeed = dv.xSpeed + temp*bounciness
          else
            dv.xSpeed = dv.xSpeed - temp*bounciness
          end
          temp = self.ySpeed
          if (dv.ySpeed > 0) then
            self.ySpeed = self.ySpeed - dv.ySpeed*bounciness
          else
            self.ySpeed = self.ySpeed + dv.ySpeed*bounciness
          end
          if (temp > 0) then
            dv.ySpeed = dv.ySpeed + temp*bounciness
          else
            dv.ySpeed = dv.ySpeed - temp*bounciness
          end
        --TODO
        -- Angle of rebound
        -- Ejection
      end
    end
  end
Attachments
collide.love
(2.57 KiB) Downloaded 197 times
http://www.atunnecliffe.com/
Bitcoins? 1LHn9yaErCakYD2iGwCyLungiC7ACxRUW8
User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: [Help] Circle Collision detection and reaction

Post by micha »

I haven't read your code, but I believe, this blog article might be, what you are looking for.
andrew.207
Prole
Posts: 19
Joined: Sat Jun 08, 2013 7:27 am
Location: Melbourne, Australia

Re: [Help] Circle Collision detection and reaction

Post by andrew.207 »

The article was well written and looked nice, but it simply doesn't work properly. When two circles collide, their combined velocity can be higher than it was before the collision, which isn't possible. I'll keep fiddling around.
http://www.atunnecliffe.com/
Bitcoins? 1LHn9yaErCakYD2iGwCyLungiC7ACxRUW8
User avatar
Banoticus
Citizen
Posts: 60
Joined: Wed Apr 04, 2012 4:01 pm
Location: London

Re: [Help] Circle Collision detection and reaction

Post by Banoticus »

I ran it and all was working well, balls were colliding fine until something happened and they became warp drives!
KaoS
Prole
Posts: 11
Joined: Sat Mar 02, 2013 2:06 am

Re: [Help] Circle Collision detection and reaction

Post by KaoS »

Hi there. I have been working on something very similar and have gotten it working. There are 2 approaches that you can use. The vector or the angle approach.

before we get started on that I noticed a few minor things what I would change in your code:

(1) on line 8 you say self.x-dv.x
I believe you should invert that to dv.x-self.x for the correct value
(2) line 14 you math max the x and y velocities of both bodies, if the x velocity is greatest in the first object and the y velocity is greatest in the second object you are subtracting unrelated velocities
(3) you are using math.cos to get the angle when math.cos uses the angle to get the fraction of x/dist. math.acos is the correct one (at least that's what I remember. Sorry if I am wrong here :) )
(4) using math.acos(x/dist) or math.asin(y/dist) or even math.atan(y/x) will give you a correct angle but that angle may be positive when it should be negative, the only way to get an accurate angle is math.atan2(y,x)

anyways looking at your code as it is makes me think you prefer the vector method. At the moment you are using whichever velocity is highest so what we need to do is use both, rather than reflecting the total velocity and breaking it down into x and y again we are going to convert x and y into towards and adjacent, bounce the towards velocity and then convert them back to x and y

Code: Select all

-- Check if any shapes are colliding with each other
  -- Objects are on top of each other if the distance between them is greater than the sum of their radii.
  -- For every shape in the shape table
  for di,dv in ipairs(shapes) do  
    if (self.id ~= dv.id) then -- If we're not colliding with ourself
      -- Distance between the two circles
      H = math.distanceBetween(self.x, self.y, dv.x, dv.y)
      local totalRelativeVelocity = math.distanceBetween(self.xSpeed, self.ySpeed, dv.xSpeed, dv.ySpeed)
      local A = dv.x - self.x -- Distance of adjacent edge
      local O = dv.y - self.y -- Distance of opposite edge
      local relativeVelocityX = self.xSpeed - dv.xSpeed -- the adjacent/x speed of self relative to dv
      local relativeVelocityY = self.ySpeed - dv.ySpeed -- the opposite/y speed of self relative to dv
      local collectiveVelocityX = (self.xSpeed + dv.xSpeed) / 2 -- the x speed at which the midpoint of both bodies is travelling
      local collectiveVelocityY = (self.ySpeed + dv.ySpeed) / 2 -- the y speed of the center
      local angleofVelocity = math.atan2(relativeVelocityY, relativeVelocityX) -- the angle that self is travelling in relative to dv
      local angleOfCollision = math.atan2(O, A) -- Angle given when a right angle triangle is created with self and dv
      local towardsVel = math.cos(angleOfCollision - angleofVelocity) * totalRelativeVelocity -- this is basically your force of hit thing
      local rotaryVel = math.sin(angleOfCollision - angleofVelocity) * totalRelativeVelocity -- this is how fast you are rotating around dv in a clockwise direction
      -- Sum of the radii
      local sumOfRadii = (self.radius*scale) + (dv.radius*scale)
      local levelOfPenetration = H - sumOfRadii -- Level of penetration (-ve if collision)

      if levelOfPenetration <= 0 and towardsVel > 0 then -- also collide if levelOfPenetration is 0 because they are still touching
        --[[basic ejection cancelled as it causes inaccuracies. The bodies will naturally move apart again
        if (self.x > dv.x) then
          self.x = self.x - levelOfPenetration
        else
          self.x = self.x + levelOfPenetration
        end
        if (self.y > dv.y) then
          self.y = self.y - levelOfPenetration
        else
          self.y = self.y + levelOfPenetration
        end]]
      
          -- Apply base speed modifications
          towardsVel = towardsVel * bounciness

          -- Convert velocities back into x and y and add the velocity of the center back into it. We add math.pi to angleOfCollision because it is in the opposite direction
          self.xSpeed = ( math.cos( angleOfCollision + math.pi ) * towardsVel + math.cos( angleOfCollision - math.pi / 2 ) * rotaryVel ) / 2 + collectiveVelocityX
          self.ySpeed = ( math.sin( angleOfCollision + math.pi ) * towardsVel + math.sin( angleOfCollision - math.pi / 2 ) * rotaryVel ) / 2 + collectiveVelocityY
      end
    end
  end
ALSO please note that you should not change self.xSpeed and self.ySpeed at this point.

if object A and object B are colliding you will calculate object A's new velocity and adjust it accordingly, then you will try to calculate object B's new velocity but you will not get a collision because now A is travelling in the opposite direction. If you by some miracle do get a collision then it will not be correctly calculated because A's velocity has changed

what you should do is

Code: Select all

-- Check if any shapes are colliding with each other
  -- Objects are on top of each other if the distance between them is greater than the sum of their radii.
  -- For every shape in the shape table
  for di,dv in ipairs(shapes) do  
    if (self.id ~= dv.id) then -- If we're not colliding with ourself
      -- Distance between the two circles
      H = math.distanceBetween(self.x, self.y, dv.x, dv.y)
      local totalRelativeVelocity = math.distanceBetween(self.xSpeed, self.ySpeed, dv.xSpeed, dv.ySpeed)
      local A = dv.x - self.x -- Distance of adjacent edge
      local O = dv.y - self.y -- Distance of opposite edge
      local relativeVelocityX = self.xSpeed - dv.xSpeed -- the adjacent/x speed of self relative to dv
      local relativeVelocityY = self.ySpeed - dv.ySpeed -- the opposite/y speed of self relative to dv
      local collectiveVelocityX = (self.xSpeed + dv.xSpeed) / 2 -- the x speed at which the midpoint of both bodies is travelling
      local collectiveVelocityY = (self.ySpeed + dv.ySpeed) / 2 -- the y speed of the center
      local angleofVelocity = math.atan2(relativeVelocityY, relativeVelocityX) -- the angle that self is travelling in relative to dv
      local angleOfCollision = math.atan2(O, A) -- Angle given when a right angle triangle is created with self and dv
      local towardsVel = math.cos(angleOfCollision - angleofVelocity) * totalRelativeVelocity -- this is basically your force of hit thing
      local rotaryVel = math.sin(angleOfCollision - angleofVelocity) * totalRelativeVelocity -- this is how fast you are rotating around dv in a clockwise direction
      -- Sum of the radii
      local sumOfRadii = (self.radius*scale) + (dv.radius*scale)
      local levelOfPenetration = H - sumOfRadii -- Level of penetration (-ve if collision)

      if levelOfPenetration <= 0 and towardsVel > 0 then -- also collide if levelOfPenetration is 0 because they are still touching
        --[[basic ejection cancelled as it causes inaccuracies. The bodies will naturally move apart again
        if (self.x > dv.x) then
          self.x = self.x - levelOfPenetration
        else
          self.x = self.x + levelOfPenetration
        end
        if (self.y > dv.y) then
          self.y = self.y - levelOfPenetration
        else
          self.y = self.y + levelOfPenetration
        end]]
      
          -- Apply base speed modifications
          towardsVel = towardsVel * bounciness

          -- Convert velocities back into x and y and add the velocity of the center back into it. We add math.pi to angleOfCollision because it is in the opposite direction
          self.newXSpeed = ( math.cos( angleOfCollision + math.pi ) * towardsVel + math.cos( angleOfCollision - math.pi / 2 ) * rotaryVel ) / 2 + collectiveVelocityX
          self.newYSpeed = ( math.sin( angleOfCollision + math.pi ) * towardsVel + math.sin( angleOfCollision - math.pi / 2 ) * rotaryVel ) / 2 + collectiveVelocityY
      end
    end
  end
  for di,dv in ipairs(shapes) do
    dv.xSpeed = dv.newXSpeed or dv.xSpeed
    dv.ySpeed = dv.newYSpeed or dv.ySpeed
  end
Post Reply

Who is online

Users browsing this forum: No registered users and 6 guests