Grainpoint/actual game/libs/collider.lua
2024-09-27 22:35:30 +02:00

499 lines
No EOL
12 KiB
Lua

-- Collider v2.0
local collider = {}
collider.colliders = {}
collider.boxIDs = {}
collider.circleIDs = {}
function collider.newBox(x, y, width, height, tag, trigger, debugColor)
local newBox = {}
newBox.x = x
newBox.y = y
newBox.width = width
newBox.height = height or newBox.width
newBox.tag = tag or 'none'
if trigger == nil then
newBox.trigger = false
else
newBox.trigger = trigger
end
newBox.debugColor = debugColor or {1,1,1}
newBox.ID = tostring('B'..#collider.boxIDs+1)
newBox.onTriggerEnter = onTriggerEnter or function(collider) end
newBox.onTriggerExit = onTriggerExit or function(collider) end
newBox.collisions = {}
newBox.lastFrameCollisions = {}
newBox.type = 'box'
newBox.superPoints = {
{
x = newBox.x,
y = newBox.y
},
{
x = newBox.x + newBox.width,
y = newBox.y + newBox.height
},
updatePoint = function(point, x, y)
newBox.superPoints[point].x = x
newBox.superPoints[point].y = y
end
}
function newBox.updatePos(newX, newY, center)
if center == nil then
center = false
end
if center == true then
newBox.x = newX - newBox.width/2
newBox.y = newY - newBox.height/2
else
newBox.x = newX
newBox.y = newY
end
newBox.superPoints.updatePoint(1, newBox.x, newBox.y)
newBox.superPoints.updatePoint(2, newBox.x + newBox.width, newBox.y + newBox.height)
end
function newBox.collidingWith(id)
return newBox.collisions[id] ~= nil
end
function newBox.getID()
return newBox.ID
end
-- NOT WORKING
function newBox.destroy()
for k,v in ipairs(collider.boxIDs) do
if v == newBox.ID then
v = nil
end
end
collider.colliders[newBox.ID] = nil
end
collider.colliders[newBox.ID] = newBox
newBox.IDindex = #collider.boxIDs + 1
collider.boxIDs[newBox.IDindex] = newBox.ID
return newBox
end
function collider.newCircle(x, y, radius, tag, trigger, debugColor)
local newCircle = {}
newCircle.x = x
newCircle.y = y
newCircle.radius = radius or 50
newCircle.tag = tag or 'none'
if trigger == nil then
newCircle.trigger = false
else
newCircle.trigger = trigger
end
newCircle.debugColor = debugColor or {1,1,1}
newCircle.ID = tostring('C'..#collider.circleIDs+1)
newCircle.onTriggerEnter = onTriggerEnter or function(collider) end
newCircle.onTriggerExit = onTriggerExit or function(collider) end
newCircle.collisions = {}
newCircle.lastFrameCollisions = {}
newCircle.type = 'circle'
function newCircle.updatePos(newX, newY)
newCircle.x = newX
newCircle.y = newY
end
function newCircle.collidingWith(id)
return newCircle.collisions[id] ~= nil
end
function newCircle.getID()
return newCircle.ID
end
-- NOT WORKING
function newCircle.destroy()
for k,v in ipairs(collider.circleIDs) do
if v == newCircle.ID then
v = nil
end
end
collider.colliders[newCircle.ID] = nil
end
collider.colliders[newCircle.ID] = newCircle
newCircle.IDindex = #collider.circleIDs + 1
collider.circleIDs[newCircle.IDindex] = newCircle.ID
return newCircle
end
function collider.update()
collider.updateBoxes()
collider.updateCircles()
end
function collider.overlapBox(boxX, boxY, width, height)
local collisions = {}
local superPoints = {
{
x = boxX,
y = boxY
},
{
x = boxX + width,
y = boxY + height
},
}
-- Against every other box
for kk,other in ipairs(collider.boxIDs) do
other = collider.colliders[other]
-- Look for an x plane and y plane intersection in superPoints
local spX = {false, false}
local spY = {false, false}
-- Loop through superPoints
for kkk,sp in ipairs(superPoints) do
if sp.x > other.superPoints[1].x and sp.x < other.superPoints[2].x then
spY[kkk] = true
end
if sp.y > other.superPoints[1].y and sp.y < other.superPoints[2].y then
spX[kkk] = true
end
end
-- Check if superPoints show a collision between my and other
if (spX[1] and spY[1]) or (spX[2] and spY[2]) or (spX[1] and spY[2]) or (spX[2] and spY[1]) then
collisions[other.ID] = other
end
end
-- Against every other circle
for kk,other in ipairs(collider.circleIDs) do
other = collider.colliders[other]
local testX = other.x
local testY = other.y
if (other.x < boxX) then
testX = boxX -- left edge
elseif (other.x > boxX + width) then
testX = boxY + width -- right edge
end
if (other.y < boxY) then
testY = boxY -- top edge
elseif (other.y > boxY + height) then
testY = boxY + height -- bottom edge
end
local distX = other.x - testX;
local distY = other.y - testY;
local distance = (distX * distX) + (distY * distY);
if distance <= other.radius * other.radius then
collisions[other.ID] = other
end
end
return collisions
end
function collider.overlapCircle(x, y, radius)
local collisions = {}
-- Against every other box
for kk,other in ipairs(collider.boxIDs) do
other = collider.colliders[other]
local testX = x
local testY = y
if (x < other.x) then
testX = other.x -- left edge
elseif (x > other.x + other.width) then
testX = other.x + other.width -- right edge
end
if (y < other.y) then
testY = other.y -- top edge
elseif (y > other.y + other.height) then
testY = other.y + other.height -- bottom edge
end
local distX = x - testX;
local distY = y - testY;
local distance = (distX * distX) + (distY * distY);
if distance <= radius * radius then
collisions[other.ID] = other
end
end
-- Against every other circle
for kk,other in ipairs(collider.circleIDs) do
other = collider.colliders[other]
local distX = x - other.x;
local distY = y - other.y;
local distance = (distX*distX) + (distY*distY);
if (distance <= (radius+other.radius) * (radius+other.radius)) then
collisions[other.ID] = other
end
end
return collisions
end
function collider.updateBoxes()
for k,v in ipairs(collider.boxIDs) do
v = collider.colliders[v]
for kk in pairs (v.collisions) do
v.collisions[kk] = nil
end
end
-- Check every box for any collisions
for k,my in ipairs(collider.boxIDs) do
my = collider.colliders[my]
-- Against every other box
for kk,other in ipairs(collider.boxIDs) do
other = collider.colliders[other]
-- Ignore if it is yourself
if kk ~= k then
-- Look for an x plane and y plane intersection in superPoints
local spX = {false, false}
local spY = {false, false}
-- Loop through superPoints
for kkk,sp in ipairs(my.superPoints) do
if sp.x > other.superPoints[1].x and sp.x < other.superPoints[2].x then
spY[kkk] = true
end
if sp.y > other.superPoints[1].y and sp.y < other.superPoints[2].y then
spX[kkk] = true
end
end
-- Check if superPoints show a collision between my and other
if (spX[1] and spY[1]) or (spX[2] and spY[2]) or (spX[1] and spY[2]) or (spX[2] and spY[1]) then
if not my.collisions[other.ID] ~= nil or not other.collisions[my.ID] ~= nil then
my.collisions[other.ID] = other
other.collisions[my.ID] = my
end
end
end
end
-- Against every other circle
for kk,other in ipairs(collider.circleIDs) do
other = collider.colliders[other]
local testX = other.x
local testY = other.y
if (other.x < my.x) then
testX = my.x -- left edge
elseif (other.x > my.x + my.width) then
testX = my.x + my.width -- right edge
end
if (other.y < my.y) then
testY = my.y -- top edge
elseif (other.y > my.y + my.height) then
testY = my.y + my.height -- bottom edge
end
local distX = other.x - testX;
local distY = other.y - testY;
local distance = (distX * distX) + (distY * distY);
if distance <= other.radius * other.radius then
my.collisions[other.ID] = other
other.collisions[my.ID] = my
end
end
end
-- Update collision status
for k,my in ipairs(collider.boxIDs) do
my = collider.colliders[my]
for kk,other in pairs(my.collisions) do
-- Check if its a new collision
if my.lastFrameCollisions[other.ID] == nil then
my.lastFrameCollisions[other.ID] = other
my.onTriggerEnter(collider.colliders[other.ID])
collider.colliders[other.ID].lastFrameCollisions[my.ID] = my
collider.colliders[other.ID].onTriggerEnter(my)
end
end
for kk,other in pairs(my.lastFrameCollisions) do
-- Check if collisions from last frame aren't there anymore
if my.collisions[other.ID] == nil then
my.lastFrameCollisions[other.ID] = nil
my.onTriggerExit(collider.colliders[other.ID])
collider.colliders[other.ID].lastFrameCollisions[my.ID] = nil
collider.colliders[other.ID].onTriggerExit(my)
end
end
end
end
function collider.updateCircles()
for k,v in ipairs(collider.circleIDs) do
v = collider.colliders[v]
for kk in pairs (v.collisions) do
v.collisions[kk] = nil
end
end
-- Check every circle
for k,my in ipairs(collider.circleIDs) do
my = collider.colliders[my]
-- Against every other box
for kk,other in ipairs(collider.boxIDs) do
other = collider.colliders[other]
local testX = my.x
local testY = my.y
if (my.x < other.x) then
testX = other.x -- left edge
elseif (my.x > other.x + other.width) then
testX = other.x + other.width -- right edge
end
if (my.y < other.y) then
testY = other.y -- top edge
elseif (my.y > other.y + other.height) then
testY = other.y + other.height -- bottom edge
end
local distX = my.x - testX;
local distY = my.y - testY;
local distance = (distX * distX) + (distY * distY);
if distance <= my.radius * my.radius then
my.collisions[other.ID] = other
other.collisions[my.ID] = my
end
end
-- Against every other circle
for kk,other in ipairs(collider.circleIDs) do
if k ~= kk then
other = collider.colliders[other]
local distX = my.x - other.x;
local distY = my.y - other.y;
local distance = (distX*distX) + (distY*distY);
if (distance <= (my.radius+other.radius) * (my.radius+other.radius)) then
my.collisions[other.ID] = other
other.collisions[my.ID] = my
end
end
end
end
-- Update collision status
for k,my in ipairs(collider.circleIDs) do
my = collider.colliders[my]
for kk,other in pairs(my.collisions) do
-- Check if its a new collision
if my.lastFrameCollisions[other.ID] == nil then
my.lastFrameCollisions[other.ID] = other
my.onTriggerEnter(collider.colliders[other.ID])
collider.colliders[other.ID].lastFrameCollisions[my.ID] = my
collider.colliders[other.ID].onTriggerEnter(my)
end
end
for kk,other in pairs(my.lastFrameCollisions) do
-- Check if collisions from last frame aren't there anymore
if my.collisions[other.ID] == nil then
my.lastFrameCollisions[other.ID] = nil
my.onTriggerExit(collider.colliders[other.ID])
collider.colliders[other.ID].lastFrameCollisions[my.ID] = nil
collider.colliders[other.ID].onTriggerExit(my)
end
end
end
end
function collider.draw()
for k,id in ipairs(collider.boxIDs) do
local v = collider.colliders[id]
if v ~= nil then
love.graphics.setColor(v.debugColor)
love.graphics.rectangle('line', v.x, v.y, v.width, v.height)
love.graphics.circle('fill', v.x + v.width/2, v.y + v.height/2, 3)
for kk,vv in ipairs(v.superPoints) do
love.graphics.circle('fill', vv.x, vv.y, 3)
end
love.graphics.print(v.ID, v.x + 5, v.y - 15)
love.graphics.print(v.tag, v.x + 5, v.y)
end
end
for k,id in ipairs(collider.circleIDs) do
local v = collider.colliders[id]
if v ~= nil then
love.graphics.setColor(v.debugColor)
love.graphics.circle('line', v.x, v.y, v.radius)
love.graphics.circle('fill', v.x, v.y, 3)
love.graphics.print(v.ID, v.x + 5, v.y - 15)
love.graphics.print(v.tag, v.x + 5, v.y)
end
end
end
return collider