Created
February 8, 2017 06:21
-
-
Save mtdowling/2bd0c31ed01d1c83dd1024f758a14460 to your computer and use it in GitHub Desktop.
A slice of some of my collision rounding code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local mod = {} | |
--- Returns true if the rects overlap. | |
-- @param a Table rect containing x1, y1, x2, y2 | |
-- @param b Table rect containing x1, y1, x2, y2 | |
-- @return bool Returns true if rects overlap. | |
mod.doRectsOverlap = function(a, b) | |
return a.x1 <= b.x2 and a.x2 >= b.x1 and a.y1 <= b.y2 and a.y2 >= b.y1 | |
end | |
--- Creates a quadrant of a rect. | |
-- @param item Item that contains x, y, w, and h. | |
-- @param addHalfW Set to true to use the right half. | |
-- @param addHalfH Set to true to use the bottom half. | |
-- @return table Returns the rect table (x1, y1, x2, y2) | |
local function createQuadrant(item, addHalfW, addHalfH) | |
local x1, y1 = item.x, item.y | |
if addHalfW then x1 = x1 + item.w * 0.5 end | |
if addHalfH then y1 = y1 + item.h * 0.5 end | |
return { | |
x1 = x1, | |
y1 = y1, | |
x2 = x1 + item.w * 0.5, | |
y2 = y1 + item.h * 0.5, | |
} | |
end | |
local function isInQuadrant(cols, addHalfW, addHalfH) | |
assert(#cols > 0) | |
local itemQuad = createQuadrant(cols[1].itemRect, addHalfW, addHalfH) | |
for _, col in ipairs(cols) do | |
if mod.doRectsOverlap(itemQuad, col._rect) and col.type ~= "cross" then | |
return true | |
end | |
end | |
return false | |
end | |
local function inUpperLeftQuad(cols) | |
return isInQuadrant(cols, false, false) | |
end | |
local function inUpperRightQuad(cols) | |
return isInQuadrant(cols, true, false) | |
end | |
local function inLowerRightQuad(cols) | |
return isInQuadrant(cols, true, true) | |
end | |
local function inLowerLeftQuad(cols) | |
return isInQuadrant(cols, false, true) | |
end | |
--- Returns a rounded collision for a movement attempt. | |
-- @param dx Attempted x movement. | |
-- @param dy Attempted y movement. | |
-- @param x Current X. | |
-- @param y Current Y. | |
-- @param cols Detected bump collisions. | |
-- @return Returns the updated x and y | |
mod.roundCollisions = function(dx, dy, x, y, cols) | |
local movement = max(abs(dx), abs(dy)) | |
-- Add and cache rects on each col. | |
for _, col in ipairs(cols) do | |
local item = col.otherRect | |
col._rect = { | |
x1 = item.x, | |
y1 = item.y, | |
x2 = item.x + item.w, | |
y2 = item.y + item.h, | |
} | |
end | |
if dx > 0 then | |
if not inUpperRightQuad(cols) then | |
if inLowerRightQuad(cols) then | |
y = y - movement | |
end | |
elseif not inLowerRightQuad(cols) then | |
y = y + movement | |
end | |
elseif dx < 0 then | |
if not inUpperLeftQuad(cols) then | |
if inLowerLeftQuad(cols) then | |
y = y - movement | |
end | |
elseif not inLowerLeftQuad(cols) then | |
y = y + movement | |
end | |
elseif dy > 0 then | |
if not inLowerRightQuad(cols) then | |
if inLowerLeftQuad(cols) then | |
x = x + movement | |
end | |
elseif not inLowerLeftQuad(cols) then | |
x = x - movement | |
end | |
elseif dy < 0 then | |
if not inUpperLeftQuad(cols) then | |
if inUpperRightQuad(cols) then | |
x = x - movement | |
end | |
elseif not inUpperRightQuad(cols) then | |
x = x + movement | |
end | |
end | |
return x, y | |
end | |
return mod |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local ecs = require "ecs" | |
local collision = require "collision" | |
local components = require "components" | |
local utils = require "utils" | |
local clamp = lume.clamp | |
--- Updates entity physics. | |
local MovementSystem = ecs.createSystem("Movement", "pos", "motion") | |
--- Default filter used for collisions. | |
MovementSystem.collisionFilter = collision.filters.default() | |
--- Invoke each collision event. | |
function MovementSystem.processCollisions(collisions) | |
for _, c in ipairs(collisions) do | |
local item, other = c.item, c.other | |
local aemitter, bemitter = item.emitter, other.emitter | |
if aemitter then aemitter:emit("collision", item, other, c) end | |
-- Only emit the collision event to `other` if other's filter accepts it. | |
if bemitter then | |
local otherFilter = other.collision and other.collision.filter | |
if not otherFilter or otherFilter(other, item) then | |
bemitter:emit("collision", other, item, c) | |
end | |
end | |
end | |
end | |
--- Checks the collisions for a given entity with the destination coordinates. | |
function MovementSystem.checkCollisions(e, ex, ey) | |
local pos = assert(e.pos, "No pos component") | |
local coll = assert(e.collision, "No collision component") | |
local box = coll.box or {0, 0} | |
if not game.play.bump.rects[e] then | |
return ex, ey, {}, 0 | |
end | |
-- Resolve bump collisions and get the collided coordinates. | |
local x, y, colls, len = game.play.bump:check( | |
e, ex + box[1], ey + box[2], coll.filter) | |
-- Adjust back to un-do the bbox adjustment. | |
x, y = x - box[1], y - box[2] | |
-- Account for crazy ass teleportation type collisions with | |
-- arrows, spikes, etc. | |
local actualMovement = math.max(math.abs(pos.x - x), math.abs(pos.y - y)) | |
local movement = math.max(math.abs(pos.x - ex), math.abs(pos.y - ey)) | |
if actualMovement > movement then | |
return pos.x, pos.y, colls, len | |
end | |
return x, y, colls, len | |
end | |
--- Move the object to the given position and invoke the given collision fns | |
function MovementSystem.move(e, x, y, collisions) | |
local collisionComponent = e.collision | |
if collisionComponent then | |
local box, pos = collisionComponent.box, e.pos | |
pos.x, pos.y = x, y | |
if game.play:tryMove(e, pos.x + box[1], pos.y + box[2]) then | |
if collisions then | |
MovementSystem.processCollisions(collisions) | |
end | |
end | |
end | |
end | |
function MovementSystem:updateEach(dt, e) | |
local coll, pos, motion = e.collision, e.pos, e.motion | |
local currentDx, currentDy = motion.dx, motion.dy | |
local dx, dy = motion.dx or 0, motion.dy or 0 | |
-- Not moving, then don't update. | |
if dx == 0 and dy == 0 then return end | |
-- Emit the move event so other systems can know that the entity moved. | |
e:send("move", e, dx, dy) | |
if coll then | |
-- Move the entity based on bump collision resolutions. | |
self:_moveTowards(e, pos, coll, dx, dy) | |
else | |
-- If this is not in bump, then just move the entity. | |
pos.x, pos.y = pos.x + dx, pos.y + dy | |
end | |
-- Remove the motion attributes. | |
if motion.dx == currentDx then motion.dx = 0 end | |
if motion.dy == currentDy then motion.dy = 0 end | |
end | |
function MovementSystem:_moveTowards(e, pos, coll, dx, dy) | |
local expectX, expectY = pos.x + dx, pos.y + dy | |
local x, y, colls = MovementSystem.checkCollisions(e, expectX, expectY) | |
local hasDx = math.abs(dx) > 1 ^ -8 | |
local hasDy = math.abs(dy) > 1 ^ -8 | |
-- Only round when not moving diagonally, when collisions are detected, and | |
-- the expected move is not what was resolved in bump. | |
if coll.roundedCorners ~= false | |
and #colls > 0 | |
and (x ~= expectX or y ~= expectY) | |
and ((not hasDx and hasDy) or (hasDx and not hasDy)) then | |
if hasDx then | |
x, y = collision.roundCollisions(dx, 0, x, y, colls) | |
else | |
x, y = collision.roundCollisions(0, dy, x, y, colls) | |
end | |
end | |
return MovementSystem.move(e, x, y, colls) | |
end | |
return MovementSystem |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment