*per pixel collision between 2 sprites
*line intersection detection
*box line intersection detection (really sprite line intersection)
*can extend lines so that you can take 2 points and test for a line like it but much longer
*bounding circle between two sprites
*bounding box between two sprites
*bounding box between two sprites with reduced/added tolerance so that you can reduce/increase size of bounding box for each sprite
*line constructor for use of lines with these functions
sprites must have x,y, and image property's where x and y donate numerical position and image donates a love image class
lines must have x1,y1,x2,y2 property's where each respective pair of x and y donates numerical postion
note: sprites must have collisionMap key as well for use with per pixel collision detection
Code: Select all
--[[
this script contains sevral usefull functions for collsion detection of maney kinds.
it uses 2 basic data typs, line and sprite
line must be comprised of x1,y1,x2,y2 as non nil numercal values
sprite must be comprised of x,y,image where x and y are numerical and image is love image class
note: a collisionMap key is needed for pixal perfect collsion. pixal perfect collsion should be used as
little as possible becuase the collisionMap takes up more memeory and per pixal collsion detection is
very slow. the collsion map may be conscted by the NewCollisionMap with the associated filepath as argument
--]]
--[[
you never know how someone wants to store there lines so i have provided a netural line class constructor
--]]
function NewLine(sx,sy,ex,ey)
return {x1=sx,y1=sy,x2=ex,y2=ey}
end
function BoundingCircleCollide(S1,S2)
local Rad1 = math.sqrt((S1.image:getWidth()/2)^2+(S1.image:getHeight()/2)^2)
local Rad2 = math.sqrt((S2.image:getWidth()/2)^2+(S2.image:getHeight()/2)^2)
local Dist = math.sqrt((S2.image:getWidth()/2-S1.image:getWidth()/2)^2+(S2.image:getHeight()/2-S1.image:getHeight()/2)^2)
return Rad1+Rad2 < Dist
end
--[[
this may seem silly but it extends the line so that no mater where the line is in a 800x600
screen, the line will always(unless the line is a point) reach the edge of the screen one way
(going from point 1 to point2). you should not need any more than that as even on the largest
screens i know of you would have to have a line of 3 pixals or less for it to mater.
if you think it matters then increse it. also if you want to detect things off screen from
very far away you may want to increse it
@param:
L-a table representing a line. must have x1,y1,x2,y2 keys as non nil numercal values
--]]
function ExtendLine(L)
local Line = L
local xinc = L.x2-L.x1
local yinc = L.y2-L.y1
Line.x2 = Line.x2+xinc*1000
Line.y2 = Line.y2+yinc*1000
return Line
end
--[[
standerd box vs. box collsion. checks for bounding box collsion between two sprites
@param:
S1-a table representing a sprite. must have x(number),y(number),image(LOVE image class)
S2-same as S1
--]]
function BoundingBoxCollide(S1,S2)
local left1=S1.x-S1.image:getWidth()/2
local left2=S2.x-S2.image:getWidth()/2
local right1=S1.x+S1.image:getWidth()/2
local right2=S2.x+S2.image:getWidth()/2
local top1=S1.y-S1.image:getHeight()/2
local top2=S2.y-S2.image:getHeight()/2
local bottom1=S1.y+S1.image:getHeight()/2
local bottom2=S2.y+S2.image:getHeight()/2
if bottom1 < top2 then return false end
if top1 > bottom2 then return false end
if right1 < left2 then return false end
if left1 > right2 then return false end
return true
end
--[[
standerd box vs. box collsion but with tollrence, so that you can shrink your bouding box for each sprite
12 is a good tolrence for 64x64 images but it will take some messing with in most cases
@param:
S1-a table representing a sprite. must have x(number),y(number),image(LOVE image class)
S2-same as S1
t1-number, tells how much to shrink bounding box by for S1
t2-same as t1 but instead for S2
--]]
function BoundingBoxCollideWT(S1,S2,t1,t2)
local left1=S1.x-S1.image:getWidth()/2+t1
local left2=S2.x-S2.image:getWidth()/2+t2
local right1=S1.x+S1.image:getWidth()/2-t1
local right2=S2.x+S2.image:getWidth()/2-t2
local top1=S1.y-S1.image:getHeight()/2+t1
local top2=S2.y-S2.image:getHeight()/2+t2
local bottom1=S1.y+S1.image:getHeight()/2-t1
local bottom2=S2.y+S2.image:getHeight()/2-t2
if bottom1 < top2 then return false end
if top1 > bottom2 then return false end
if right1 < left2 then return false end
if left1 > right2 then return false end
return true
end
--[[
this is mainly for BoxLineCollsion but you can use it for other stuff to
checks for intersection of two lines
@param:
L1-a table representing a line. must have x1,y1,x2,y2 keys as non nil numercal values
L2-same as L1
--]]
function LineIntersets(L1,L2)
local denom = ((L2.y2-L2.y1)*(L1.x2-L1.x1)-((L2.x2-L2.x1)*(L1.y2-L1.y1)))
local numerator = ((L2.x2-L2.x1)*(L1.y1-L2.y1)-((L2.y2-L2.y1)*(L1.x1-L2.x1)))
local numerator2 = ((L1.x2-L1.x1)*(L1.y1-L2.y1)-((L1.y2-L1.y1)*(L1.x1-L2.x1)))
if denom == 0 then
return false
end
local ua = numerator/denom
local ub = numerator2/denom
return (ua>=0 and ua <= 1 and ub >= 0 and ub <= 1)
end
--[[
checks for intersection of line and bounding box(of sprite)
@param:
S-a table representing a sprite. must have x(number),y(number),image(LOVE image class)
L-a table representing a line. must have x1,y1,x2,y2 keys as non nil numercal values
--]]
function BoxLineCollsion(S,L)
local left=S.x-S..image:getWidth()/2+12
local right=S.x+S..image:getWidth()/2-12
local top=S.y-S.image:getHeight()/2+12
local bottom=S.y+S.image:getHeight()/2-12
if LineIntersets(NewLine(left,top,right,top),L) then return true
elseif LineIntersets(NewLine(left,bottom,right,bottom),L) then return true
elseif LineIntersets(NewLine(right,top,right,bottom),L) then return true
--no need to check thrid as there is no way to go though 1 line and 1 line only
end
return false
end
--[[
makes pixal map of image for pixal perfect collsion detection
@param:
imageFileName- string donating file path to image
--]]
function NewCollisionMap( imageFileName )
local imageData = love.image.newImageData( imageFileName )
local width = imageData:getWidth()
local height = imageData:getHeight()
local collisionMap = {}
-- Build a collision map as a table of row tables that contains 1's and 0's.
-- A 1 means the pixel at this position is non-transparent and 0 means it
-- transparent.
for y = 1, height do
collisionMap[y] = {}
for x = 1, width do
-- Use -1 since getPixel() starts indexing at 0 not 1 like Lua.
local r, g, b, a = imageData:getPixel( x-1, y-1 )
if a == 0 then
collisionMap[y][x] = 0
else
collisionMap[y][x] = 1
end
end
end
return collisionMap
end
--[[
checks for pixal perfect collsion detection between two sprites
@param:
sprite1-a table representing a sprite. must have x(number),y(number),
image(LOVE image class),collisionMap(class donated by function above)
sprite2-same as sprite1
--]]
function PerPixelCollision( sprite1, sprite2 )
-- First, we do a simple bounding box collision check. This will let
-- us know if the two sprites overlap in any way.
if not ( (sprite1.x + sprite1.image:getWidth() > sprite2.x) and
(sprite1.x < sprite2.x + sprite2.image:getWidth()) and
(sprite1.y + sprite1.image:getHeight() > sprite2.y) and
(sprite1.y < sprite2.y + sprite2.image:getHeight()) ) then
return false
end
-- If we made it this far, our two sprites definitely touch or overlap,
-- but that doesn't mean that that we have an actual collision between
-- two non-transparent pixels.
-- By default, sprite1 scans sprite2 for a pixel collision per line, so
-- if sprite1 is taller, swap the sprites around so the shorter one is
-- scanning the taller one. This will result in less work in cases where
-- they initially overlap but ultimately do not collide at the pixel level.
if sprite1.image:getHeight() > sprite2.image:getHeight() then
objTemp = sprite1
sprite1 = sprite2
sprite1 = objTemp
end
-- Loop through each row of sprite1's collision map and check it against
-- sprite2's corresponding collision map row.
for indexY = 1, sprite1.image:getHeight() do
local screenY = math.floor( (sprite1.y + indexY) - 1 )
if screenY > sprite2.y and screenY <= sprite2.y + sprite2.image:getHeight() then
-- Some, or all, of the current row (Y) of sprite1's collision map overlaps
-- sprite2's collision map. Calculate the start and end indices (X) for each
-- row, so we can test this area of overlap for a collision of
-- non-transparent pixels.
local y1 = math.floor( indexY )
local y2 = 1
if screenY > sprite2.y then
y2 = math.floor( screenY - sprite2.y )
elseif screenY < sprite2.y then
y2 = math.floor( sprite2.y - screenY )
end
local sprite1Index1 = 1
local sprite1Index2 = sprite1.image:getWidth()
local sprite2Index1 = 1
local sprite2Index2 = sprite2.image:getWidth()
if sprite1.x < sprite2.x then
sprite1Index1 = math.floor( sprite2.x - sprite1.x ) + 1
sprite1Index2 = sprite1.image:getWidth()
sprite2Index1 = 1
sprite2Index2 = math.floor( (sprite1.x + sprite1.image:getWidth()) - sprite2.x ) + 1
-- If the sprites being tested are of different sizes it's possible
-- for this index to get too big - so clamp it.
if sprite2Index2 > sprite2.image:getWidth() then
sprite2Index2 = sprite2.image:getWidth()
end
elseif sprite1.x > sprite2.x then
sprite1Index1 = 1
sprite1Index2 = math.floor( (sprite2.x + sprite2.image:getWidth()) - sprite1.x ) + 1
-- If the sprites being tested are of different sizes it's possible
-- for this index to get too big - so clamp it.
if sprite1Index2 > sprite1.image:getWidth() then
sprite1Index2 = sprite1.image:getWidth()
end
sprite2Index1 = math.floor( sprite1.x - sprite2.x ) + 1
sprite2Index2 = sprite2.image:getWidth()
else -- sprite1.x == sprite2.x
-- If the two sprites have the same x position - the width of
-- overlap is simply the shortest width.
shortest = sprite1.image:getWidth()
if sprite2.image:getWidth() < shortest then
shortest = sprite2.image:getWidth()
end
sprite1Index1 = 1
sprite1Index2 = shortest
sprite2Index1 = 1
sprite2Index2 = shortest
end
local index1 = sprite1Index1
local index2 = sprite2Index1
while index1 < sprite1Index2 and index2 < sprite2Index2 do
if sprite1.collisionMap[y1][index1] == 1 and sprite2.collisionMap[y2][index2] == 1 then
return true -- We have a collision of two non-transparent pixels!
end
index1 = index1 + 1
index2 = index2 + 1
end
end
end
return false -- We do NOT have a collision of two non-transparent pixels.
end