local Physics = {} local bump = require("lib.bump") local pprint = require("lib.pprint") local Vec = require("lib.brinevector") -- TODO: Tweak bump world `cellSize` at runtime, when the map switches Physics.required_groups = {"physical", "collider"} function Physics:init() self.bump = bump.newWorld() self.boundCollisionFilter = function(...) return self:collisionFilter(...) end end function Physics:onMapSwitch(map) map:bump_init(self.bump) end local function getColliderBounds(e) local x = e.collider[1] local y = e.collider[2] local w = math.abs(e.collider[3] - e.collider[1]) local h = math.abs(e.collider[4] - e.collider[2]) if e.pos then x = x + e.pos.x y = y + e.pos.y end return x, y, w, h end function Physics:addToGroup(group, e) if group == "collider" then self.bump:add(e, getColliderBounds(e)) end end function Physics:removeFromGroup(group, e) if group == "collider" then self.bump:remove(e) end end function Physics:collisionFilter(entity, other) if entity.hidden then return end if other.hidden then return end local bolt = entity.bolt or other.bolt local vel = entity.vel or other.vel local player = entity.bolts or other.bolts if bolt and player then return "cross" elseif bolt then return "bounce" elseif (vel or vel) and not (entity.bolts and other.bolts) then return "slide" end end function Physics:resolveCollisions(e, dt) local targetPos = e.pos + e.vel * dt local ox = e.collider[1] local oy = e.collider[2] self.bump:update(e, e.pos.x+ox, e.pos.y+oy) local x, y, cols = self.bump:move(e, targetPos.x+ox, targetPos.y+oy, self.boundCollisionFilter) local skip_moving = false if #cols > 0 then e.pos.x = x - ox e.pos.y = y - oy skip_moving = true end for _, col in ipairs(cols) do if col.type == "cross" then local player = col.other.bolts and col.other or e local bolt = col.other.bolt and col.other or e if player and bolt and bolt.owner ~= player then self.pool:emit("hitPlayer", player, bolt) end elseif col.type == "bounce" then -- sx and sy and just number for flipped the direction when something -- bounces. -- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy local sx = 1 - 2*math.abs(col.normal.x) local sy = 1 - 2*math.abs(col.normal.y) e.vel.x = e.vel.x * sx e.vel.y = e.vel.y * sy if e.acc then e.acc.x = e.acc.x * sx e.acc.y = e.acc.y * sy end skip_moving = true end end return skip_moving end function Physics:update(dt) for _, e in ipairs(self.pool.groups.physical.entities) do if e.acc then e.vel = e.vel + e.acc * dt end if e.friction then e.vel = e.vel * (1 - math.min(e.friction, 1)) ^ dt end local skip_moving = false if self.pool.groups.collider.hasEntity[e] then skip_moving = self:resolveCollisions(e, dt) end if not skip_moving then if e.max_speed then e.pos = e.pos + e.vel:trim(e.max_speed) * dt else e.pos = e.pos + e.vel * dt end end end end function Physics:castRay(from, to) local items = self.bump:querySegmentWithCoords(from.x, from.y, to.x, to.y, function(item) return not item.vel end) for _, item in ipairs(items) do if item.nx ~= 0 or item.ny ~= 0 then local pos = Vec(item.x1, item.y1) local normal = Vec(item.nx, item.ny) return pos, normal end end end function Physics:project(from, direction, distance) local key_points = {} table.insert(key_points, from) local distance_traveled = 0 local position = from -- While the ray hasen't traveled the required amount while math.abs(distance_traveled - distance) > 0.01 do -- Check if it collides with anything local destination = position + direction * (distance-distance_traveled) local intersect, normal = self:castRay(position, destination) local new_position = intersect or destination -- Update how much the ray has moved in total distance_traveled = distance_traveled + (position - new_position).length position = new_position -- If it collided change its travel direction if normal then -- sx and sy and just number for flipped the direction when something -- bounces. -- When `normal.x` is zero, sx = 1, otherwise sx = -1. Same with sy direction.x = direction.x * (1 - 2*math.abs(normal.x)) direction.y = direction.y * (1 - 2*math.abs(normal.y)) end -- Save this point where it stopped/collided table.insert(key_points, new_position) end return key_points end function Physics:queryRect(pos, size, filter) return self.bump:queryRect(pos.x, pos.y, size.x, size.y, filter) end return Physics