I’ve been updating my Touchline game, getting it ready to submit to the App Store and have hit a snag with my collision detection. I’ve just switched to a new collision detection algorithm for my line to circle collisions, but something’s wrong. Most of the time it’s detecting collisions perfectly, but occasionally I can manage to stick a line straight through a circle and nothing seems to be detected. See the following video for an example:
http://cl.ly/2M3X2P3T2f3w0L2U2G0x
My other issue is that I need to be able to tell how far away a given circle is, and I’m not sure if this algorithm will provide me with that. Any help any of you maths geniuses could provide me would be very much appreciated.
I got the algorithm I’m using from the following Stack Overflow post: http://stackoverflow.com/questions/1073336/circle-line-collision-detection
Code for a simplified test app:
------------------------------------------
-- MAIN
------------------------------------------
function setup()
touches = {}
enemies = {}
-- generate some random enemies
for i = 1,5 do
local x = math.random(0,WIDTH)
local y = math.random(0,HEIGHT)
table.insert(enemies, Enemy(x,y))
end
ship = Ship()
end
function draw()
background(40, 40, 50)
local startTouch = nil
local endTouch = nil
for k,touch in pairs(touches) do
endTouch = startTouch
startTouch = touch
end
if endTouch then
-- if we have two touches, tell the ship about them
ship:setPoints(startTouch, endTouch)
ship:draw()
end
for i,v in ipairs(enemies) do
-- check for collisions and update enemies
if endTouch then ship:collide(v) end
v:draw()
end
end
function touched(touch)
touches[touch.id] = vec2(touch.x, touch.y)
end
HIT = 4
NOT_HIT = 1
------------------------------------------
-- SHIP
------------------------------------------
Ship = class()
function Ship:init(col)
self:reset(col)
end
function Ship:reset(col)
self.startPoint = nil
self.endPoint = nil
self.lineLength = 0.0
self.lineWidth = 12
self.currentWidth = 12
self.lightColor = col or color(184, 248, 24, 255)
self.darkColor = col or color(184, 248, 24, 255)
end
function Ship:setPoints(startp, endp)
-- keep track of which point is leftmost
if startp.x < endp.x then
self.startPoint = startp
self.endPoint = endp
else
self.startPoint = endp
self.endPoint = startp
end
end
function Ship:draw()
self.lineLength = self.startPoint:dist( self.endPoint )
pushStyle()
noSmooth()
lineCapMode(PROJECT)
stroke(self.darkColor)
strokeWidth(self.currentWidth)
line(self.startPoint.x, self.startPoint.y, self.endPoint.x, self.endPoint.y)
stroke(self.lightColor)
strokeWidth(self.currentWidth - 4)
line(self.startPoint.x, self.startPoint.y, self.endPoint.x, self.endPoint.y)
popStyle()
end
function Ship:collide(v)
-- v is a circle
local d = self.endPoint - self.startPoint
local f = self.startPoint - v.position
local r = v:halfWidth()
local a = d:dot( d )
local b = 2*f:dot( d )
local c = f:dot( f ) - r*r
local discriminant = b*b-4*a*c
if( discriminant < 0 ) then
-- no intersection
v:style(NOT_HIT)
else
-- ray didn't totally miss sphere,
-- so there is a solution to
-- the equation.
discriminant = math.sqrt( discriminant )
local t1 = (-b + discriminant)/(2*a)
local t2 = (-b - discriminant)/(2*a)
if( t1 >= 0 and t1 <= 1 ) then
-- solution on is ON THE RAY.
v:style(HIT)
else
-- solution "out of range" of ray
v:style(NOT_HIT)
end
-- use t2 for second point
end
end
------------------------------------------
-- ENEMY
------------------------------------------
Enemy = class()
function Enemy:init(x,y)
self.position = vec2(x,y)
self.size = math.random(20,200)
self.distance = 0
self.c = color(255, 0, 0, 255)
end
function Enemy:draw()
fill(self.c)
noStroke()
ellipseMode(CENTER)
ellipse(self.position.x, self.position.y, self.size)
font("Futura-Medium")
fontSize(20)
fill(255, 255, 255, 255)
text(self.distance, self.position.x, self.position.y)
end
function Enemy:style(num)
-- restyle the enemy based on how we're colliding
if num == NOT_HIT then
self.c = color(255, 0, 0, 255)
else
self.distance = "HIT"
self.c = color(0, 255, 162, 255)
end
end
-- register the current distance from the line to this enemy
function Enemy:skim(distance)
self.distance = distance
end
function Enemy:miss()
self.distance = 0
end
function Enemy:halfWidth()
return self.size * 0.5
end